<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="pandoc" />

<meta name="viewport" content="width=device-width, initial-scale=1">

<meta name="author" content="Kirill Müller, Hadley Wickham" />


<title>Extending tibble</title>



<style type="text/css">code{white-space: pre;}</style>
<style type="text/css" data-origin="pandoc">
a.sourceLine { display: inline-block; line-height: 1.25; }
a.sourceLine { pointer-events: none; color: inherit; text-decoration: inherit; }
a.sourceLine:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode { white-space: pre; position: relative; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
code.sourceCode { white-space: pre-wrap; }
a.sourceLine { text-indent: -1em; padding-left: 1em; }
}
pre.numberSource a.sourceLine
  { position: relative; left: -4em; }
pre.numberSource a.sourceLine::before
  { content: attr(data-line-number);
    position: relative; left: -1em; text-align: right; vertical-align: baseline;
    border: none; pointer-events: all; display: inline-block;
    -webkit-touch-callout: none; -webkit-user-select: none;
    -khtml-user-select: none; -moz-user-select: none;
    -ms-user-select: none; user-select: none;
    padding: 0 4px; width: 4em;
    color: #aaaaaa;
  }
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa;  padding-left: 4px; }
div.sourceCode
  {  }
@media screen {
a.sourceLine::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */

</style>
<script>
// apply pandoc div.sourceCode style to pre.sourceCode instead
(function() {
  var sheets = document.styleSheets;
  for (var i = 0; i < sheets.length; i++) {
    if (sheets[i].ownerNode.dataset["origin"] !== "pandoc") continue;
    try { var rules = sheets[i].cssRules; } catch (e) { continue; }
    for (var j = 0; j < rules.length; j++) {
      var rule = rules[j];
      // check if there is a div.sourceCode rule
      if (rule.type !== rule.STYLE_RULE || rule.selectorText !== "div.sourceCode") continue;
      var style = rule.style.cssText;
      // check if color or background-color is set
      if (rule.style.color === '' || rule.style.backgroundColor === '') continue;
      // replace div.sourceCode by a pre.sourceCode rule
      sheets[i].deleteRule(j);
      sheets[i].insertRule('pre.sourceCode{' + style + '}', j);
    }
  }
})();
</script>



<style type="text/css">body {
background-color: #fff;
margin: 1em auto;
max-width: 700px;
overflow: visible;
padding-left: 2em;
padding-right: 2em;
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.35;
}
#header {
text-align: center;
}
#TOC {
clear: both;
margin: 0 0 10px 10px;
padding: 4px;
width: 400px;
border: 1px solid #CCCCCC;
border-radius: 5px;
background-color: #f6f6f6;
font-size: 13px;
line-height: 1.3;
}
#TOC .toctitle {
font-weight: bold;
font-size: 15px;
margin-left: 5px;
}
#TOC ul {
padding-left: 40px;
margin-left: -1.5em;
margin-top: 5px;
margin-bottom: 5px;
}
#TOC ul ul {
margin-left: -2em;
}
#TOC li {
line-height: 16px;
}
table {
margin: 1em auto;
border-width: 1px;
border-color: #DDDDDD;
border-style: outset;
border-collapse: collapse;
}
table th {
border-width: 2px;
padding: 5px;
border-style: inset;
}
table td {
border-width: 1px;
border-style: inset;
line-height: 18px;
padding: 5px 5px;
}
table, table th, table td {
border-left-style: none;
border-right-style: none;
}
table thead, table tr.even {
background-color: #f7f7f7;
}
p {
margin: 0.5em 0;
}
blockquote {
background-color: #f6f6f6;
padding: 0.25em 0.75em;
}
hr {
border-style: solid;
border: none;
border-top: 1px solid #777;
margin: 28px 0;
}
dl {
margin-left: 0;
}
dl dd {
margin-bottom: 13px;
margin-left: 13px;
}
dl dt {
font-weight: bold;
}
ul {
margin-top: 0;
}
ul li {
list-style: circle outside;
}
ul ul {
margin-bottom: 0;
}
pre, code {
background-color: #f7f7f7;
border-radius: 3px;
color: #333;
white-space: pre-wrap; 
}
pre {
border-radius: 3px;
margin: 5px 0px 10px 0px;
padding: 10px;
}
pre:not([class]) {
background-color: #f7f7f7;
}
code {
font-family: Consolas, Monaco, 'Courier New', monospace;
font-size: 85%;
}
p > code, li > code {
padding: 2px 0px;
}
div.figure {
text-align: center;
}
img {
background-color: #FFFFFF;
padding: 2px;
border: 1px solid #DDDDDD;
border-radius: 3px;
border: 1px solid #CCCCCC;
margin: 0 5px;
}
h1 {
margin-top: 0;
font-size: 35px;
line-height: 40px;
}
h2 {
border-bottom: 4px solid #f7f7f7;
padding-top: 10px;
padding-bottom: 2px;
font-size: 145%;
}
h3 {
border-bottom: 2px solid #f7f7f7;
padding-top: 10px;
font-size: 120%;
}
h4 {
border-bottom: 1px solid #f7f7f7;
margin-left: 8px;
font-size: 105%;
}
h5, h6 {
border-bottom: 1px solid #ccc;
font-size: 105%;
}
a {
color: #0033dd;
text-decoration: none;
}
a:hover {
color: #6666ff; }
a:visited {
color: #800080; }
a:visited:hover {
color: #BB00BB; }
a[href^="http:"] {
text-decoration: underline; }
a[href^="https:"] {
text-decoration: underline; }

code > span.kw { color: #555; font-weight: bold; } 
code > span.dt { color: #902000; } 
code > span.dv { color: #40a070; } 
code > span.bn { color: #d14; } 
code > span.fl { color: #d14; } 
code > span.ch { color: #d14; } 
code > span.st { color: #d14; } 
code > span.co { color: #888888; font-style: italic; } 
code > span.ot { color: #007020; } 
code > span.al { color: #ff0000; font-weight: bold; } 
code > span.fu { color: #900; font-weight: bold; }  code > span.er { color: #a61717; background-color: #e3d2d2; } 
</style>




</head>

<body>




<h1 class="title toc-ignore">Extending tibble</h1>
<h4 class="author">Kirill Müller, Hadley Wickham</h4>



<p>To extend the tibble package for new types of columnar data, you need to understand how printing works. The presentation of a column in a tibble is powered by four S3 generics:</p>
<ul>
<li><code>type_sum()</code> determines what goes into the column header.</li>
<li><code>pillar_shaft()</code> determines what goes into the body of the column.</li>
<li><code>is_vector_s3()</code> and <code>obj_sum()</code> are used when rendering list columns.</li>
</ul>
<p>If you have written an S3 or S4 class that can be used as a column, you can override these generics to make sure your data prints well in a tibble. To start, you must import the <code>pillar</code> package that powers the printing of tibbles. Either add <code>pillar</code> to the <code>Imports:</code> section of your <code>DESCRIPTION</code>, or simply call:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb1-1" data-line-number="1">usethis<span class="op">::</span><span class="kw">use_package</span>(<span class="st">&quot;pillar&quot;</span>)</a></code></pre></div>
<p>This short vignette assumes a package that implements an S3 class <code>&quot;latlon&quot;</code> and uses <code>roxygen2</code> to create documentation and the <code>NAMESPACE</code> file. For this vignette to work we need to attach pillar:</p>
<div id="prerequisites" class="section level2">
<h2>Prerequisites</h2>
<p>We define a class <code>&quot;latlon&quot;</code> that encodes geographic coordinates in a complex number. For simplicity, the values are printed as degrees and minutes only.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb2-1" data-line-number="1"><span class="co">#' @export</span></a>
<a class="sourceLine" id="cb2-2" data-line-number="2">latlon &lt;-<span class="st"> </span><span class="cf">function</span>(lat, lon) {</a>
<a class="sourceLine" id="cb2-3" data-line-number="3">  <span class="kw">as_latlon</span>(<span class="kw">complex</span>(<span class="dt">real =</span> lon, <span class="dt">imaginary =</span> lat))</a>
<a class="sourceLine" id="cb2-4" data-line-number="4">}</a>
<a class="sourceLine" id="cb2-5" data-line-number="5"></a>
<a class="sourceLine" id="cb2-6" data-line-number="6"><span class="co">#' @export</span></a>
<a class="sourceLine" id="cb2-7" data-line-number="7">as_latlon &lt;-<span class="st"> </span><span class="cf">function</span>(x) {</a>
<a class="sourceLine" id="cb2-8" data-line-number="8">  <span class="kw">structure</span>(x, <span class="dt">class =</span> <span class="st">&quot;latlon&quot;</span>)</a>
<a class="sourceLine" id="cb2-9" data-line-number="9">}</a>
<a class="sourceLine" id="cb2-10" data-line-number="10"></a>
<a class="sourceLine" id="cb2-11" data-line-number="11"><span class="co">#' @export</span></a>
<a class="sourceLine" id="cb2-12" data-line-number="12">c.latlon &lt;-<span class="st"> </span><span class="cf">function</span>(x, ...) {</a>
<a class="sourceLine" id="cb2-13" data-line-number="13">  <span class="kw">as_latlon</span>(<span class="kw">NextMethod</span>())</a>
<a class="sourceLine" id="cb2-14" data-line-number="14">}</a>
<a class="sourceLine" id="cb2-15" data-line-number="15"></a>
<a class="sourceLine" id="cb2-16" data-line-number="16"><span class="co">#' @export</span></a>
<a class="sourceLine" id="cb2-17" data-line-number="17"><span class="st">`</span><span class="dt">[.latlon</span><span class="st">`</span> &lt;-<span class="st"> </span><span class="cf">function</span>(x, i) {</a>
<a class="sourceLine" id="cb2-18" data-line-number="18">  <span class="kw">as_latlon</span>(<span class="kw">NextMethod</span>())</a>
<a class="sourceLine" id="cb2-19" data-line-number="19">}</a>
<a class="sourceLine" id="cb2-20" data-line-number="20"></a>
<a class="sourceLine" id="cb2-21" data-line-number="21"><span class="co">#' @export</span></a>
<a class="sourceLine" id="cb2-22" data-line-number="22">format.latlon &lt;-<span class="st"> </span><span class="cf">function</span>(x, ..., <span class="dt">formatter =</span> deg_min) {</a>
<a class="sourceLine" id="cb2-23" data-line-number="23">  x_valid &lt;-<span class="st"> </span><span class="kw">which</span>(<span class="op">!</span><span class="kw">is.na</span>(x))</a>
<a class="sourceLine" id="cb2-24" data-line-number="24"></a>
<a class="sourceLine" id="cb2-25" data-line-number="25">  lat &lt;-<span class="st"> </span><span class="kw">unclass</span>(<span class="kw">Im</span>(x[x_valid]))</a>
<a class="sourceLine" id="cb2-26" data-line-number="26">  lon &lt;-<span class="st"> </span><span class="kw">unclass</span>(<span class="kw">Re</span>(x[x_valid]))</a>
<a class="sourceLine" id="cb2-27" data-line-number="27"></a>
<a class="sourceLine" id="cb2-28" data-line-number="28">  ret &lt;-<span class="st"> </span><span class="kw">rep</span>(<span class="st">&quot;&lt;NA&gt;&quot;</span>, <span class="kw">length</span>(x))</a>
<a class="sourceLine" id="cb2-29" data-line-number="29">  ret[x_valid] &lt;-<span class="st"> </span><span class="kw">paste</span>(</a>
<a class="sourceLine" id="cb2-30" data-line-number="30">    <span class="kw">formatter</span>(lat, <span class="kw">c</span>(<span class="st">&quot;N&quot;</span>, <span class="st">&quot;S&quot;</span>)),</a>
<a class="sourceLine" id="cb2-31" data-line-number="31">    <span class="kw">formatter</span>(lon, <span class="kw">c</span>(<span class="st">&quot;E&quot;</span>, <span class="st">&quot;W&quot;</span>))</a>
<a class="sourceLine" id="cb2-32" data-line-number="32">  )</a>
<a class="sourceLine" id="cb2-33" data-line-number="33">  <span class="kw">format</span>(ret, <span class="dt">justify =</span> <span class="st">&quot;right&quot;</span>)</a>
<a class="sourceLine" id="cb2-34" data-line-number="34">}</a>
<a class="sourceLine" id="cb2-35" data-line-number="35"></a>
<a class="sourceLine" id="cb2-36" data-line-number="36">deg_min &lt;-<span class="st"> </span><span class="cf">function</span>(x, pm) {</a>
<a class="sourceLine" id="cb2-37" data-line-number="37">  sign &lt;-<span class="st"> </span><span class="kw">sign</span>(x)</a>
<a class="sourceLine" id="cb2-38" data-line-number="38">  x &lt;-<span class="st"> </span><span class="kw">abs</span>(x)</a>
<a class="sourceLine" id="cb2-39" data-line-number="39">  deg &lt;-<span class="st"> </span><span class="kw">trunc</span>(x)</a>
<a class="sourceLine" id="cb2-40" data-line-number="40">  x &lt;-<span class="st"> </span>x <span class="op">-</span><span class="st"> </span>deg</a>
<a class="sourceLine" id="cb2-41" data-line-number="41">  min &lt;-<span class="st"> </span><span class="kw">round</span>(x <span class="op">*</span><span class="st"> </span><span class="dv">60</span>)</a>
<a class="sourceLine" id="cb2-42" data-line-number="42"></a>
<a class="sourceLine" id="cb2-43" data-line-number="43">  ret &lt;-<span class="st"> </span><span class="kw">sprintf</span>(<span class="st">&quot;%d°%.2d'%s&quot;</span>, deg, min, pm[<span class="kw">ifelse</span>(sign <span class="op">&gt;=</span><span class="st"> </span><span class="dv">0</span>, <span class="dv">1</span>, <span class="dv">2</span>)])</a>
<a class="sourceLine" id="cb2-44" data-line-number="44">  <span class="kw">format</span>(ret, <span class="dt">justify =</span> <span class="st">&quot;right&quot;</span>)</a>
<a class="sourceLine" id="cb2-45" data-line-number="45">}</a>
<a class="sourceLine" id="cb2-46" data-line-number="46"></a>
<a class="sourceLine" id="cb2-47" data-line-number="47"><span class="co">#' @export</span></a>
<a class="sourceLine" id="cb2-48" data-line-number="48">print.latlon &lt;-<span class="st"> </span><span class="cf">function</span>(x, ...) {</a>
<a class="sourceLine" id="cb2-49" data-line-number="49">  <span class="kw">cat</span>(<span class="kw">format</span>(x), <span class="dt">sep =</span> <span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span>)</a>
<a class="sourceLine" id="cb2-50" data-line-number="50">  <span class="kw">invisible</span>(x)</a>
<a class="sourceLine" id="cb2-51" data-line-number="51">}</a>
<a class="sourceLine" id="cb2-52" data-line-number="52"></a>
<a class="sourceLine" id="cb2-53" data-line-number="53"><span class="kw">latlon</span>(<span class="fl">32.7102978</span>, <span class="fl">-117.1704058</span>)</a></code></pre></div>
<pre><code>## 32°43'N 117°10'W</code></pre>
<p>More methods are needed to make this class fully compatible with data frames, see e.g. the <a href="https://github.com/tidyverse/hms/">hms</a> package for a more complete example.</p>
</div>
<div id="using-in-a-tibble" class="section level2">
<h2>Using in a tibble</h2>
<p>Columns on this class can be used in a tibble right away, but the output will be less than ideal:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb4-1" data-line-number="1"><span class="kw">library</span>(tibble)</a>
<a class="sourceLine" id="cb4-2" data-line-number="2">data &lt;-<span class="st"> </span><span class="kw">tibble</span>(</a>
<a class="sourceLine" id="cb4-3" data-line-number="3">  <span class="dt">venue =</span> <span class="st">&quot;rstudio::conf&quot;</span>,</a>
<a class="sourceLine" id="cb4-4" data-line-number="4">  <span class="dt">year  =</span> <span class="dv">2017</span><span class="op">:</span><span class="dv">2019</span>,</a>
<a class="sourceLine" id="cb4-5" data-line-number="5">  <span class="dt">loc   =</span> <span class="kw">latlon</span>(</a>
<a class="sourceLine" id="cb4-6" data-line-number="6">    <span class="kw">c</span>(<span class="fl">28.3411783</span>, <span class="fl">32.7102978</span>, <span class="ot">NA</span>),</a>
<a class="sourceLine" id="cb4-7" data-line-number="7">    <span class="kw">c</span>(<span class="op">-</span><span class="fl">81.5480348</span>, <span class="fl">-117.1704058</span>, <span class="ot">NA</span>)</a>
<a class="sourceLine" id="cb4-8" data-line-number="8">  ),</a>
<a class="sourceLine" id="cb4-9" data-line-number="9">  <span class="dt">paths =</span> <span class="kw">list</span>(</a>
<a class="sourceLine" id="cb4-10" data-line-number="10">    loc[<span class="dv">1</span>],</a>
<a class="sourceLine" id="cb4-11" data-line-number="11">    <span class="kw">c</span>(loc[<span class="dv">1</span>], loc[<span class="dv">2</span>]),</a>
<a class="sourceLine" id="cb4-12" data-line-number="12">    loc[<span class="dv">2</span>]</a>
<a class="sourceLine" id="cb4-13" data-line-number="13">  )</a>
<a class="sourceLine" id="cb4-14" data-line-number="14">)</a>
<a class="sourceLine" id="cb4-15" data-line-number="15"></a>
<a class="sourceLine" id="cb4-16" data-line-number="16">data</a></code></pre></div>
<pre><code>## # A tibble: 3 x 4
##   venue          year loc              paths   
##   &lt;chr&gt;         &lt;int&gt; &lt;latlon&gt;         &lt;list&gt;  
## 1 rstudio::conf  2017 28°20'N  81°33'W &lt;latlon&gt;
## 2 rstudio::conf  2018 32°43'N 117°10'W &lt;latlon&gt;
## 3 rstudio::conf  2019             &lt;NA&gt; &lt;latlon&gt;</code></pre>
<p>(The <code>paths</code> column is a list that contains arbitrary data, in our case <code>latlon</code> vectors. A list column is a powerful way to attach hierarchical or unstructured data to an observation in a data frame.)</p>
<p>The output has three main problems:</p>
<ol style="list-style-type: decimal">
<li>The column type of the <code>loc</code> column is displayed as <code>&lt;S3: latlon&gt;</code>. This default formatting works reasonably well for any kind of object, but the generated output may be too wide and waste precious space when displaying the tibble.</li>
<li>The values in the <code>loc</code> column are formatted as complex numbers (the underlying storage), without using the <code>format()</code> method we have defined. This is by design.</li>
<li>The cells in the <code>paths</code> column are also displayed as <code>&lt;S3: latlon&gt;</code>.</li>
</ol>
<p>In the remainder I’ll show how to fix these problems, and also how to implement rendering that adapts to the available width.</p>
</div>
<div id="fixing-the-data-type" class="section level2">
<h2>Fixing the data type</h2>
<p>To display <code>&lt;geo&gt;</code> as data type, we need to override the <code>type_sum()</code> method. This method should return a string that can be used in a column header. For your own classes, strive for an evocative abbreviation that’s under 6 characters.</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb6-1" data-line-number="1"><span class="co">#' @importFrom pillar type_sum</span></a>
<a class="sourceLine" id="cb6-2" data-line-number="2"><span class="co">#' @export</span></a>
<a class="sourceLine" id="cb6-3" data-line-number="3">type_sum.latlon &lt;-<span class="st"> </span><span class="cf">function</span>(x) {</a>
<a class="sourceLine" id="cb6-4" data-line-number="4">  <span class="st">&quot;geo&quot;</span></a>
<a class="sourceLine" id="cb6-5" data-line-number="5">}</a></code></pre></div>
<p>Because the value shown there doesn’t depend on the data, we just return a constant. (For date-times, the column info will eventually contain information about the timezone, see <a href="https://github.com/r-lib/pillar/pull/53">#53</a>.)</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb7-1" data-line-number="1">data</a></code></pre></div>
<pre><code>## # A tibble: 3 x 4
##   venue          year loc              paths 
##   &lt;chr&gt;         &lt;int&gt; &lt;geo&gt;            &lt;list&gt;
## 1 rstudio::conf  2017 28°20'N  81°33'W &lt;geo&gt; 
## 2 rstudio::conf  2018 32°43'N 117°10'W &lt;geo&gt; 
## 3 rstudio::conf  2019             &lt;NA&gt; &lt;geo&gt;</code></pre>
</div>
<div id="rendering-the-value" class="section level2">
<h2>Rendering the value</h2>
<p>To use our format method for rendering, we implement the <code>pillar_shaft()</code> method for our class. (A <a href="https://en.wikipedia.org/wiki/Column#Nomenclature"><em>pillar</em></a> is mainly a <em>shaft</em> (decorated with an <em>ornament</em>), with a <em>capital</em> above and a <em>base</em> below. Multiple pillars form a <em>colonnade</em>, which can be stacked in multiple <em>tiers</em>. This is the motivation behind the names in our API.)</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb9-1" data-line-number="1"><span class="co">#' @importFrom pillar pillar_shaft</span></a>
<a class="sourceLine" id="cb9-2" data-line-number="2"><span class="co">#' @export</span></a>
<a class="sourceLine" id="cb9-3" data-line-number="3">pillar_shaft.latlon &lt;-<span class="st"> </span><span class="cf">function</span>(x, ...) {</a>
<a class="sourceLine" id="cb9-4" data-line-number="4">  out &lt;-<span class="st"> </span><span class="kw">format</span>(x)</a>
<a class="sourceLine" id="cb9-5" data-line-number="5">  out[<span class="kw">is.na</span>(x)] &lt;-<span class="st"> </span><span class="ot">NA</span></a>
<a class="sourceLine" id="cb9-6" data-line-number="6">  pillar<span class="op">::</span><span class="kw">new_pillar_shaft_simple</span>(out, <span class="dt">align =</span> <span class="st">&quot;right&quot;</span>)</a>
<a class="sourceLine" id="cb9-7" data-line-number="7">}</a></code></pre></div>
<p>The simplest variant calls our <code>format()</code> method, everything else is handled by pillar, in particular by the <code>new_pillar_shaft_simple()</code> helper. Note how the <code>align</code> argument affects the alignment of NA values and of the column name and type.</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb10-1" data-line-number="1">data</a></code></pre></div>
<pre><code>## # A tibble: 3 x 4
##   venue          year              loc paths 
##   &lt;chr&gt;         &lt;int&gt;            &lt;geo&gt; &lt;list&gt;
## 1 rstudio::conf  2017 28°20'N  81°33'W &lt;geo&gt; 
## 2 rstudio::conf  2018 32°43'N 117°10'W &lt;geo&gt; 
## 3 rstudio::conf  2019               NA &lt;geo&gt;</code></pre>
<p>We could also use left alignment and indent only the <code>NA</code> values:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb12-1" data-line-number="1"><span class="co">#' @importFrom pillar pillar_shaft</span></a>
<a class="sourceLine" id="cb12-2" data-line-number="2"><span class="co">#' @export</span></a>
<a class="sourceLine" id="cb12-3" data-line-number="3">pillar_shaft.latlon &lt;-<span class="st"> </span><span class="cf">function</span>(x, ...) {</a>
<a class="sourceLine" id="cb12-4" data-line-number="4">  out &lt;-<span class="st"> </span><span class="kw">format</span>(x)</a>
<a class="sourceLine" id="cb12-5" data-line-number="5">  out[<span class="kw">is.na</span>(x)] &lt;-<span class="st"> </span><span class="ot">NA</span></a>
<a class="sourceLine" id="cb12-6" data-line-number="6">  pillar<span class="op">::</span><span class="kw">new_pillar_shaft_simple</span>(out, <span class="dt">align =</span> <span class="st">&quot;left&quot;</span>, <span class="dt">na_indent =</span> <span class="dv">5</span>)</a>
<a class="sourceLine" id="cb12-7" data-line-number="7">}</a>
<a class="sourceLine" id="cb12-8" data-line-number="8"></a>
<a class="sourceLine" id="cb12-9" data-line-number="9">data</a></code></pre></div>
<pre><code>## # A tibble: 3 x 4
##   venue          year loc              paths 
##   &lt;chr&gt;         &lt;int&gt; &lt;geo&gt;            &lt;list&gt;
## 1 rstudio::conf  2017 28°20'N  81°33'W &lt;geo&gt; 
## 2 rstudio::conf  2018 32°43'N 117°10'W &lt;geo&gt; 
## 3 rstudio::conf  2019      NA          &lt;geo&gt;</code></pre>
</div>
<div id="adaptive-rendering" class="section level2">
<h2>Adaptive rendering</h2>
<p>If there is not enough space to render the values, the formatted values are truncated with an ellipsis. This doesn’t currently apply to our class, because we haven’t specified a minimum width for our values:</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb14-1" data-line-number="1"><span class="kw">print</span>(data, <span class="dt">width =</span> <span class="dv">35</span>)</a></code></pre></div>
<pre><code>## # A tibble: 3 x 4
##   venue  year loc             
##   &lt;chr&gt; &lt;int&gt; &lt;geo&gt;           
## 1 rstu…  2017 28°20'N  81°33'W
## 2 rstu…  2018 32°43'N 117°10'W
## 3 rstu…  2019      NA         
## # … with 1 more variable:
## #   paths &lt;list&gt;</code></pre>
<p>If we specify a minimum width when constructing the shaft, the <code>loc</code> column will be truncated:</p>
<div class="sourceCode" id="cb16"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb16-1" data-line-number="1"><span class="co">#' @importFrom pillar pillar_shaft</span></a>
<a class="sourceLine" id="cb16-2" data-line-number="2"><span class="co">#' @export</span></a>
<a class="sourceLine" id="cb16-3" data-line-number="3">pillar_shaft.latlon &lt;-<span class="st"> </span><span class="cf">function</span>(x, ...) {</a>
<a class="sourceLine" id="cb16-4" data-line-number="4">  out &lt;-<span class="st"> </span><span class="kw">format</span>(x)</a>
<a class="sourceLine" id="cb16-5" data-line-number="5">  out[<span class="kw">is.na</span>(x)] &lt;-<span class="st"> </span><span class="ot">NA</span></a>
<a class="sourceLine" id="cb16-6" data-line-number="6">  pillar<span class="op">::</span><span class="kw">new_pillar_shaft_simple</span>(out, <span class="dt">align =</span> <span class="st">&quot;right&quot;</span>, <span class="dt">min_width =</span> <span class="dv">10</span>)</a>
<a class="sourceLine" id="cb16-7" data-line-number="7">}</a>
<a class="sourceLine" id="cb16-8" data-line-number="8"></a>
<a class="sourceLine" id="cb16-9" data-line-number="9"><span class="kw">print</span>(data, <span class="dt">width =</span> <span class="dv">35</span>)</a></code></pre></div>
<pre><code>## # A tibble: 3 x 4
##   venue     year          loc paths
##   &lt;chr&gt;    &lt;int&gt;        &lt;geo&gt; &lt;lis&gt;
## 1 rstudio…  2017 28°20'N  81… &lt;geo&gt;
## 2 rstudio…  2018 32°43'N 117… &lt;geo&gt;
## 3 rstudio…  2019           NA &lt;geo&gt;</code></pre>
<p>This may be useful for character data, but for lat-lon data we may prefer to show full degrees and remove the minutes if the available space is not enough to show accurate values. A more sophisticated implementation of the <code>pillar_shaft()</code> method is required to achieve this:</p>
<div class="sourceCode" id="cb18"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb18-1" data-line-number="1"><span class="co">#' @importFrom pillar pillar_shaft</span></a>
<a class="sourceLine" id="cb18-2" data-line-number="2"><span class="co">#' @export</span></a>
<a class="sourceLine" id="cb18-3" data-line-number="3">pillar_shaft.latlon &lt;-<span class="st"> </span><span class="cf">function</span>(x, ...) {</a>
<a class="sourceLine" id="cb18-4" data-line-number="4">  deg &lt;-<span class="st"> </span><span class="kw">format</span>(x, <span class="dt">formatter =</span> deg)</a>
<a class="sourceLine" id="cb18-5" data-line-number="5">  deg[<span class="kw">is.na</span>(x)] &lt;-<span class="st"> </span>pillar<span class="op">::</span><span class="kw">style_na</span>(<span class="st">&quot;NA&quot;</span>)</a>
<a class="sourceLine" id="cb18-6" data-line-number="6">  deg_min &lt;-<span class="st"> </span><span class="kw">format</span>(x)</a>
<a class="sourceLine" id="cb18-7" data-line-number="7">  deg_min[<span class="kw">is.na</span>(x)] &lt;-<span class="st"> </span>pillar<span class="op">::</span><span class="kw">style_na</span>(<span class="st">&quot;NA&quot;</span>)</a>
<a class="sourceLine" id="cb18-8" data-line-number="8">  pillar<span class="op">::</span><span class="kw">new_pillar_shaft</span>(</a>
<a class="sourceLine" id="cb18-9" data-line-number="9">    <span class="kw">list</span>(<span class="dt">deg =</span> deg, <span class="dt">deg_min =</span> deg_min),</a>
<a class="sourceLine" id="cb18-10" data-line-number="10">    <span class="dt">width =</span> pillar<span class="op">::</span><span class="kw">get_max_extent</span>(deg_min),</a>
<a class="sourceLine" id="cb18-11" data-line-number="11">    <span class="dt">min_width =</span> pillar<span class="op">::</span><span class="kw">get_max_extent</span>(deg),</a>
<a class="sourceLine" id="cb18-12" data-line-number="12">    <span class="dt">subclass =</span> <span class="st">&quot;pillar_shaft_latlon&quot;</span></a>
<a class="sourceLine" id="cb18-13" data-line-number="13">  )</a>
<a class="sourceLine" id="cb18-14" data-line-number="14">}</a></code></pre></div>
<p>Here, <code>pillar_shaft()</code> returns an object of the <code>&quot;pillar_shaft_latlon&quot;</code> class created by the generic <code>new_pillar_shaft()</code> constructor. This object contains the necessary information to render the values, and also minimum and maximum width values. For simplicity, both formattings are pre-rendered, and the minimum and maximum widths are computed from there. Note that we also need to take care of <code>NA</code> values explicitly. (<code>get_max_extent()</code> is a helper that computes the maximum display width occupied by the values in a character vector.)</p>
<p>For completeness, the code that implements the degree-only formatting looks like this:</p>
<div class="sourceCode" id="cb19"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb19-1" data-line-number="1">deg &lt;-<span class="st"> </span><span class="cf">function</span>(x, pm) {</a>
<a class="sourceLine" id="cb19-2" data-line-number="2">  sign &lt;-<span class="st"> </span><span class="kw">sign</span>(x)</a>
<a class="sourceLine" id="cb19-3" data-line-number="3">  x &lt;-<span class="st"> </span><span class="kw">abs</span>(x)</a>
<a class="sourceLine" id="cb19-4" data-line-number="4">  deg &lt;-<span class="st"> </span><span class="kw">round</span>(x)</a>
<a class="sourceLine" id="cb19-5" data-line-number="5"></a>
<a class="sourceLine" id="cb19-6" data-line-number="6">  ret &lt;-<span class="st"> </span><span class="kw">sprintf</span>(<span class="st">&quot;%d°%s&quot;</span>, deg, pm[<span class="kw">ifelse</span>(sign <span class="op">&gt;=</span><span class="st"> </span><span class="dv">0</span>, <span class="dv">1</span>, <span class="dv">2</span>)])</a>
<a class="sourceLine" id="cb19-7" data-line-number="7">  <span class="kw">format</span>(ret, <span class="dt">justify =</span> <span class="st">&quot;right&quot;</span>)</a>
<a class="sourceLine" id="cb19-8" data-line-number="8">}</a></code></pre></div>
<p>All that’s left to do is to implement a <code>format()</code> method for our new <code>&quot;pillar_shaft_latlon&quot;</code> class. This method will be called with a <code>width</code> argument, which then determines which of the formattings to choose:</p>
<div class="sourceCode" id="cb20"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb20-1" data-line-number="1"><span class="co">#' @export</span></a>
<a class="sourceLine" id="cb20-2" data-line-number="2">format.pillar_shaft_latlon &lt;-<span class="st"> </span><span class="cf">function</span>(x, width, ...) {</a>
<a class="sourceLine" id="cb20-3" data-line-number="3">  <span class="cf">if</span> (<span class="kw">all</span>(crayon<span class="op">::</span><span class="kw">col_nchar</span>(x<span class="op">$</span>deg_min) <span class="op">&lt;=</span><span class="st"> </span>width)) {</a>
<a class="sourceLine" id="cb20-4" data-line-number="4">    ornament &lt;-<span class="st"> </span>x<span class="op">$</span>deg_min</a>
<a class="sourceLine" id="cb20-5" data-line-number="5">  } <span class="cf">else</span> {</a>
<a class="sourceLine" id="cb20-6" data-line-number="6">    ornament &lt;-<span class="st"> </span>x<span class="op">$</span>deg</a>
<a class="sourceLine" id="cb20-7" data-line-number="7">  }</a>
<a class="sourceLine" id="cb20-8" data-line-number="8"></a>
<a class="sourceLine" id="cb20-9" data-line-number="9">  pillar<span class="op">::</span><span class="kw">new_ornament</span>(ornament)</a>
<a class="sourceLine" id="cb20-10" data-line-number="10">}</a>
<a class="sourceLine" id="cb20-11" data-line-number="11"></a>
<a class="sourceLine" id="cb20-12" data-line-number="12">data</a></code></pre></div>
<pre><code>## Warning: The `subclass` argument to `new_pillar_shaft()` is deprecated, please use the `class` argument.
## This warning is displayed once per session.</code></pre>
<pre><code>## # A tibble: 3 x 4
##   venue          year loc              paths 
##   &lt;chr&gt;         &lt;int&gt; &lt;geo&gt;            &lt;list&gt;
## 1 rstudio::conf  2017 28°20'N  81°33'W &lt;geo&gt; 
## 2 rstudio::conf  2018 32°43'N 117°10'W &lt;geo&gt; 
## 3 rstudio::conf  2019 NA               &lt;geo&gt;</code></pre>
<div class="sourceCode" id="cb23"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb23-1" data-line-number="1"><span class="kw">print</span>(data, <span class="dt">width =</span> <span class="dv">35</span>)</a></code></pre></div>
<pre><code>## # A tibble: 3 x 4
##   venue     year loc          paths
##   &lt;chr&gt;    &lt;int&gt; &lt;geo&gt;        &lt;lis&gt;
## 1 rstudio…  2017 28°N  82°W   &lt;geo&gt;
## 2 rstudio…  2018 33°N 117°W   &lt;geo&gt;
## 3 rstudio…  2019 NA           &lt;geo&gt;</code></pre>
</div>
<div id="adding-color" class="section level2">
<h2>Adding color</h2>
<p>Both <code>new_pillar_shaft_simple()</code> and <code>new_ornament()</code> accept ANSI escape codes for coloring, emphasis, or other ways of highlighting text on terminals that support it. Some formattings are predefined, e.g. <code>style_subtle()</code> displays text in a light gray. For default data types, this style is used for insignificant digits. We’ll be formatting the degree and minute signs in a subtle style, because they serve only as separators. You can also use the <a href="https://cran.r-project.org/package=crayon">crayon</a> package to add custom formattings to your output.</p>
<div class="sourceCode" id="cb25"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb25-1" data-line-number="1"><span class="co">#' @importFrom pillar pillar_shaft</span></a>
<a class="sourceLine" id="cb25-2" data-line-number="2"><span class="co">#' @export</span></a>
<a class="sourceLine" id="cb25-3" data-line-number="3">pillar_shaft.latlon &lt;-<span class="st"> </span><span class="cf">function</span>(x, ...) {</a>
<a class="sourceLine" id="cb25-4" data-line-number="4">  out &lt;-<span class="st"> </span><span class="kw">format</span>(x, <span class="dt">formatter =</span> deg_min_color)</a>
<a class="sourceLine" id="cb25-5" data-line-number="5">  out[<span class="kw">is.na</span>(x)] &lt;-<span class="st"> </span><span class="ot">NA</span></a>
<a class="sourceLine" id="cb25-6" data-line-number="6">  pillar<span class="op">::</span><span class="kw">new_pillar_shaft_simple</span>(out, <span class="dt">align =</span> <span class="st">&quot;left&quot;</span>, <span class="dt">na_indent =</span> <span class="dv">5</span>)</a>
<a class="sourceLine" id="cb25-7" data-line-number="7">}</a>
<a class="sourceLine" id="cb25-8" data-line-number="8"></a>
<a class="sourceLine" id="cb25-9" data-line-number="9">deg_min_color &lt;-<span class="st"> </span><span class="cf">function</span>(x, pm) {</a>
<a class="sourceLine" id="cb25-10" data-line-number="10">  sign &lt;-<span class="st"> </span><span class="kw">sign</span>(x)</a>
<a class="sourceLine" id="cb25-11" data-line-number="11">  x &lt;-<span class="st"> </span><span class="kw">abs</span>(x)</a>
<a class="sourceLine" id="cb25-12" data-line-number="12">  deg &lt;-<span class="st"> </span><span class="kw">trunc</span>(x)</a>
<a class="sourceLine" id="cb25-13" data-line-number="13">  x &lt;-<span class="st"> </span>x <span class="op">-</span><span class="st"> </span>deg</a>
<a class="sourceLine" id="cb25-14" data-line-number="14">  rad &lt;-<span class="st"> </span><span class="kw">round</span>(x <span class="op">*</span><span class="st"> </span><span class="dv">60</span>)</a>
<a class="sourceLine" id="cb25-15" data-line-number="15">  ret &lt;-<span class="st"> </span><span class="kw">sprintf</span>(</a>
<a class="sourceLine" id="cb25-16" data-line-number="16">    <span class="st">&quot;%d%s%.2d%s%s&quot;</span>,</a>
<a class="sourceLine" id="cb25-17" data-line-number="17">    deg,</a>
<a class="sourceLine" id="cb25-18" data-line-number="18">    pillar<span class="op">::</span><span class="kw">style_subtle</span>(<span class="st">&quot;°&quot;</span>),</a>
<a class="sourceLine" id="cb25-19" data-line-number="19">    rad,</a>
<a class="sourceLine" id="cb25-20" data-line-number="20">    pillar<span class="op">::</span><span class="kw">style_subtle</span>(<span class="st">&quot;'&quot;</span>),</a>
<a class="sourceLine" id="cb25-21" data-line-number="21">    pm[<span class="kw">ifelse</span>(sign <span class="op">&gt;=</span><span class="st"> </span><span class="dv">0</span>, <span class="dv">1</span>, <span class="dv">2</span>)]</a>
<a class="sourceLine" id="cb25-22" data-line-number="22">  )</a>
<a class="sourceLine" id="cb25-23" data-line-number="23">  ret[<span class="kw">is.na</span>(x)] &lt;-<span class="st"> &quot;&quot;</span></a>
<a class="sourceLine" id="cb25-24" data-line-number="24">  <span class="kw">format</span>(ret, <span class="dt">justify =</span> <span class="st">&quot;right&quot;</span>)</a>
<a class="sourceLine" id="cb25-25" data-line-number="25">}</a>
<a class="sourceLine" id="cb25-26" data-line-number="26"></a>
<a class="sourceLine" id="cb25-27" data-line-number="27">data</a></code></pre></div>
<pre><code>## # A tibble: 3 x 4
##   venue          year loc              paths 
##   &lt;chr&gt;         &lt;int&gt; &lt;geo&gt;            &lt;list&gt;
## 1 rstudio::conf  2017 28°20'N  81°33'W &lt;geo&gt; 
## 2 rstudio::conf  2018 32°43'N 117°10'W &lt;geo&gt; 
## 3 rstudio::conf  2019      NA          &lt;geo&gt;</code></pre>
<p>Currently, ANSI escapes are not rendered in vignettes, so the display here isn’t much different from earlier examples. This may change in the future.</p>
</div>
<div id="fixing-list-columns" class="section level2">
<h2>Fixing list columns</h2>
<p>To tweak the output in the <code>paths</code> column, we simply need to indicate that our class is an S3 vector:</p>
<div class="sourceCode" id="cb27"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb27-1" data-line-number="1"><span class="co">#' @importFrom pillar is_vector_s3</span></a>
<a class="sourceLine" id="cb27-2" data-line-number="2"><span class="co">#' @export</span></a>
<a class="sourceLine" id="cb27-3" data-line-number="3">is_vector_s3.latlon &lt;-<span class="st"> </span><span class="cf">function</span>(x) <span class="ot">TRUE</span></a>
<a class="sourceLine" id="cb27-4" data-line-number="4"></a>
<a class="sourceLine" id="cb27-5" data-line-number="5">data</a></code></pre></div>
<pre><code>## # A tibble: 3 x 4
##   venue          year loc              paths    
##   &lt;chr&gt;         &lt;int&gt; &lt;geo&gt;            &lt;list&gt;   
## 1 rstudio::conf  2017 28°20'N  81°33'W &lt;geo [1]&gt;
## 2 rstudio::conf  2018 32°43'N 117°10'W &lt;geo [2]&gt;
## 3 rstudio::conf  2019      NA          &lt;geo [1]&gt;</code></pre>
<p>This is picked up by the default implementation of <code>obj_sum()</code>, which then shows the type and the length in brackets. If your object is built on top of an atomic vector the default will be adequate. You, will, however, need to provide an <code>obj_sum()</code> method for your class if your object is vectorised and built on top of a list.</p>
<p>An example of an object of this type in base R is <code>POSIXlt</code>: it is a list with 9 components.</p>
<div class="sourceCode" id="cb29"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb29-1" data-line-number="1">time &lt;-<span class="st"> </span><span class="kw">as.POSIXlt</span>(<span class="st">&quot;2018-12-20 23:29:51&quot;</span>, <span class="dt">tz =</span> <span class="st">&quot;CET&quot;</span>)</a>
<a class="sourceLine" id="cb29-2" data-line-number="2">x &lt;-<span class="st"> </span><span class="kw">as.POSIXlt</span>(time <span class="op">+</span><span class="st"> </span><span class="kw">c</span>(<span class="dv">0</span>, <span class="dv">60</span>, <span class="dv">3600</span>))</a>
<a class="sourceLine" id="cb29-3" data-line-number="3"><span class="kw">str</span>(<span class="kw">unclass</span>(x))</a></code></pre></div>
<pre><code>## List of 11
##  $ sec   : num [1:3] 51 51 51
##  $ min   : int [1:3] 29 30 29
##  $ hour  : int [1:3] 23 23 0
##  $ mday  : int [1:3] 20 20 21
##  $ mon   : int [1:3] 11 11 11
##  $ year  : int [1:3] 118 118 118
##  $ wday  : int [1:3] 4 4 5
##  $ yday  : int [1:3] 353 353 354
##  $ isdst : int [1:3] 0 0 0
##  $ zone  : chr [1:3] &quot;CET&quot; &quot;CET&quot; &quot;CET&quot;
##  $ gmtoff: int [1:3] 3600 3600 3600
##  - attr(*, &quot;tzone&quot;)= chr [1:3] &quot;CET&quot; &quot;CET&quot; &quot;CEST&quot;</code></pre>
<p>But it pretends to be a vector with 3 elements:</p>
<div class="sourceCode" id="cb31"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb31-1" data-line-number="1">x</a></code></pre></div>
<pre><code>## [1] &quot;2018-12-20 23:29:51 CET&quot; &quot;2018-12-20 23:30:51 CET&quot;
## [3] &quot;2018-12-21 00:29:51 CET&quot;</code></pre>
<div class="sourceCode" id="cb33"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb33-1" data-line-number="1"><span class="kw">length</span>(x)</a></code></pre></div>
<pre><code>## [1] 3</code></pre>
<div class="sourceCode" id="cb35"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb35-1" data-line-number="1"><span class="kw">str</span>(x)</a></code></pre></div>
<pre><code>##  POSIXlt[1:3], format: &quot;2018-12-20 23:29:51&quot; &quot;2018-12-20 23:30:51&quot; ...</code></pre>
<p>So we need to define a method that returns a character vector the same length as <code>x</code>:</p>
<div class="sourceCode" id="cb37"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb37-1" data-line-number="1"><span class="co">#' @importFrom pillar obj_sum</span></a>
<a class="sourceLine" id="cb37-2" data-line-number="2"><span class="co">#' @export</span></a>
<a class="sourceLine" id="cb37-3" data-line-number="3">obj_sum.POSIXlt &lt;-<span class="st"> </span><span class="cf">function</span>(x) {</a>
<a class="sourceLine" id="cb37-4" data-line-number="4">  <span class="kw">rep</span>(<span class="st">&quot;POSIXlt&quot;</span>, <span class="kw">length</span>(x))</a>
<a class="sourceLine" id="cb37-5" data-line-number="5">}</a></code></pre></div>
</div>
<div id="testing" class="section level2">
<h2>Testing</h2>
<p>If you want to test the output of your code, you can compare it with a known state recorded in a text file. For this, pillar offers the <code>expect_known_display()</code> expectation which requires and works best with the testthat package. Make sure that the output is generated only by your package to avoid inconsistencies when external code is updated. Here, this means that you test only the shaft portion of the pillar, and not the entire pillar or even a tibble that contains a column with your data type!</p>
<p>The tests work best with the testthat package:</p>
<div class="sourceCode" id="cb38"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb38-1" data-line-number="1"><span class="kw">library</span>(testthat)</a></code></pre></div>
<p>The code below will compare the output of <code>pillar_shaft(data$loc)</code> with known output stored in the <code>latlon.txt</code> file. The first run warns because the file doesn’t exist yet.</p>
<div class="sourceCode" id="cb39"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb39-1" data-line-number="1"><span class="kw">test_that</span>(<span class="st">&quot;latlon pillar matches known output&quot;</span>, {</a>
<a class="sourceLine" id="cb39-2" data-line-number="2">  pillar<span class="op">::</span><span class="kw">expect_known_display</span>(</a>
<a class="sourceLine" id="cb39-3" data-line-number="3">    <span class="kw">pillar_shaft</span>(data<span class="op">$</span>loc),</a>
<a class="sourceLine" id="cb39-4" data-line-number="4">    <span class="dt">file =</span> <span class="st">&quot;latlon.txt&quot;</span></a>
<a class="sourceLine" id="cb39-5" data-line-number="5">  )</a>
<a class="sourceLine" id="cb39-6" data-line-number="6">})</a></code></pre></div>
<p>From the second run on, the printing will be compared with the file:</p>
<div class="sourceCode" id="cb40"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb40-1" data-line-number="1"><span class="kw">test_that</span>(<span class="st">&quot;latlon pillar matches known output&quot;</span>, {</a>
<a class="sourceLine" id="cb40-2" data-line-number="2">  pillar<span class="op">::</span><span class="kw">expect_known_display</span>(</a>
<a class="sourceLine" id="cb40-3" data-line-number="3">    <span class="kw">pillar_shaft</span>(data<span class="op">$</span>loc),</a>
<a class="sourceLine" id="cb40-4" data-line-number="4">    <span class="dt">file =</span> <span class="st">&quot;latlon.txt&quot;</span></a>
<a class="sourceLine" id="cb40-5" data-line-number="5">  )</a>
<a class="sourceLine" id="cb40-6" data-line-number="6">})</a></code></pre></div>
<p>However, if we look at the file we’ll notice strange things: The output contains ANSI escapes!</p>
<div class="sourceCode" id="cb41"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb41-1" data-line-number="1"><span class="kw">readLines</span>(<span class="st">&quot;latlon.txt&quot;</span>)</a></code></pre></div>
<pre><code>## [1] &quot;28\033[90m°\033[39m20\033[90m'\033[39mN  81\033[90m°\033[39m33\033[90m'\033[39mW&quot;
## [2] &quot;32\033[90m°\033[39m43\033[90m'\033[39mN 117\033[90m°\033[39m10\033[90m'\033[39mW&quot;
## [3] &quot;     \033[31mNA\033[39m         &quot;</code></pre>
<p>We can turn them off by passing <code>crayon = FALSE</code> to the expectation, but we need to run twice again:</p>
<div class="sourceCode" id="cb43"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb43-1" data-line-number="1"><span class="kw">library</span>(testthat)</a>
<a class="sourceLine" id="cb43-2" data-line-number="2"><span class="kw">test_that</span>(<span class="st">&quot;latlon pillar matches known output&quot;</span>, {</a>
<a class="sourceLine" id="cb43-3" data-line-number="3">  pillar<span class="op">::</span><span class="kw">expect_known_display</span>(</a>
<a class="sourceLine" id="cb43-4" data-line-number="4">    <span class="kw">pillar_shaft</span>(data<span class="op">$</span>loc),</a>
<a class="sourceLine" id="cb43-5" data-line-number="5">    <span class="dt">file =</span> <span class="st">&quot;latlon.txt&quot;</span>,</a>
<a class="sourceLine" id="cb43-6" data-line-number="6">    <span class="dt">crayon =</span> <span class="ot">FALSE</span></a>
<a class="sourceLine" id="cb43-7" data-line-number="7">  )</a>
<a class="sourceLine" id="cb43-8" data-line-number="8">})</a></code></pre></div>
<pre><code>## Error: Test failed: 'latlon pillar matches known output'
## * `print(eval_tidy(object))` has changed from known value recorded in 'latlon.txt'.
## 3/3 mismatches
## x[1]: &quot;28°20'N  81°33'W&quot;
## y[1]: &quot;28\033[90m°\033[39m20\033[90m'\033[39mN  81\033[90m°\033[39m33\033[90m'\033[39mW&quot;
## 
## x[2]: &quot;32°43'N 117°10'W&quot;
## y[2]: &quot;32\033[90m°\033[39m43\033[90m'\033[39mN 117\033[90m°\033[39m10\033[90m'\033[39mW&quot;
## 
## x[3]: &quot;     NA         &quot;
## y[3]: &quot;     \033[31mNA\033[39m         &quot;</code></pre>
<div class="sourceCode" id="cb45"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb45-1" data-line-number="1"><span class="kw">test_that</span>(<span class="st">&quot;latlon pillar matches known output&quot;</span>, {</a>
<a class="sourceLine" id="cb45-2" data-line-number="2">  pillar<span class="op">::</span><span class="kw">expect_known_display</span>(</a>
<a class="sourceLine" id="cb45-3" data-line-number="3">    <span class="kw">pillar_shaft</span>(data<span class="op">$</span>loc),</a>
<a class="sourceLine" id="cb45-4" data-line-number="4">    <span class="dt">file =</span> <span class="st">&quot;latlon.txt&quot;</span>,</a>
<a class="sourceLine" id="cb45-5" data-line-number="5">    <span class="dt">crayon =</span> <span class="ot">FALSE</span></a>
<a class="sourceLine" id="cb45-6" data-line-number="6">  )</a>
<a class="sourceLine" id="cb45-7" data-line-number="7">})</a>
<a class="sourceLine" id="cb45-8" data-line-number="8"></a>
<a class="sourceLine" id="cb45-9" data-line-number="9"><span class="kw">readLines</span>(<span class="st">&quot;latlon.txt&quot;</span>)</a></code></pre></div>
<pre><code>## [1] &quot;28°20'N  81°33'W&quot; &quot;32°43'N 117°10'W&quot; &quot;     NA         &quot;</code></pre>
<p>You may want to create a series of output files for different scenarios:</p>
<ul>
<li>Colored vs. plain (to simplify viewing differences)</li>
<li>With or without special Unicode characters (if your output uses them)</li>
<li>Different widths</li>
</ul>
<p>For this it is helpful to create your own expectation function. Use the tidy evaluation framework to make sure that construction and printing happens at the right time:</p>
<div class="sourceCode" id="cb47"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb47-1" data-line-number="1">expect_known_latlon_display &lt;-<span class="st"> </span><span class="cf">function</span>(x, file_base) {</a>
<a class="sourceLine" id="cb47-2" data-line-number="2">  quo &lt;-<span class="st"> </span>rlang<span class="op">::</span><span class="kw">quo</span>(pillar<span class="op">::</span><span class="kw">pillar_shaft</span>(x))</a>
<a class="sourceLine" id="cb47-3" data-line-number="3">  pillar<span class="op">::</span><span class="kw">expect_known_display</span>(</a>
<a class="sourceLine" id="cb47-4" data-line-number="4">    <span class="op">!!</span><span class="st"> </span>quo,</a>
<a class="sourceLine" id="cb47-5" data-line-number="5">    <span class="dt">file =</span> <span class="kw">paste0</span>(file_base, <span class="st">&quot;.txt&quot;</span>)</a>
<a class="sourceLine" id="cb47-6" data-line-number="6">  )</a>
<a class="sourceLine" id="cb47-7" data-line-number="7">  pillar<span class="op">::</span><span class="kw">expect_known_display</span>(</a>
<a class="sourceLine" id="cb47-8" data-line-number="8">    <span class="op">!!</span><span class="st"> </span>quo,</a>
<a class="sourceLine" id="cb47-9" data-line-number="9">    <span class="dt">file =</span> <span class="kw">paste0</span>(file_base, <span class="st">&quot;-bw.txt&quot;</span>),</a>
<a class="sourceLine" id="cb47-10" data-line-number="10">    <span class="dt">crayon =</span> <span class="ot">FALSE</span></a>
<a class="sourceLine" id="cb47-11" data-line-number="11">  )</a>
<a class="sourceLine" id="cb47-12" data-line-number="12">}</a></code></pre></div>
<div class="sourceCode" id="cb48"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb48-1" data-line-number="1"><span class="kw">test_that</span>(<span class="st">&quot;latlon pillar matches known output&quot;</span>, {</a>
<a class="sourceLine" id="cb48-2" data-line-number="2">  <span class="kw">expect_known_latlon_display</span>(data<span class="op">$</span>loc, <span class="dt">file_base =</span> <span class="st">&quot;latlon&quot;</span>)</a>
<a class="sourceLine" id="cb48-3" data-line-number="3">})</a></code></pre></div>
<pre><code>## Error: Test failed: 'latlon pillar matches known output'
## * `print(eval_tidy(object))` has changed from known value recorded in 'latlon.txt'.
## 3/3 mismatches
## x[1]: &quot;28\033[90m°\033[39m20\033[90m'\033[39mN  81\033[90m°\033[39m33\033[90m'\033[39mW&quot;
## y[1]: &quot;28°20'N  81°33'W&quot;
## 
## x[2]: &quot;32\033[90m°\033[39m43\033[90m'\033[39mN 117\033[90m°\033[39m10\033[90m'\033[39mW&quot;
## y[2]: &quot;32°43'N 117°10'W&quot;
## 
## x[3]: &quot;     \033[31mNA\033[39m         &quot;
## y[3]: &quot;     NA         &quot;</code></pre>
<div class="sourceCode" id="cb50"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb50-1" data-line-number="1"><span class="kw">readLines</span>(<span class="st">&quot;latlon.txt&quot;</span>)</a></code></pre></div>
<pre><code>## [1] &quot;28\033[90m°\033[39m20\033[90m'\033[39mN  81\033[90m°\033[39m33\033[90m'\033[39mW&quot;
## [2] &quot;32\033[90m°\033[39m43\033[90m'\033[39mN 117\033[90m°\033[39m10\033[90m'\033[39mW&quot;
## [3] &quot;     \033[31mNA\033[39m         &quot;</code></pre>
<div class="sourceCode" id="cb52"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb52-1" data-line-number="1"><span class="kw">readLines</span>(<span class="st">&quot;latlon-bw.txt&quot;</span>)</a></code></pre></div>
<pre><code>## [1] &quot;28°20'N  81°33'W&quot; &quot;32°43'N 117°10'W&quot; &quot;     NA         &quot;</code></pre>
<p>Learn more about the tidyeval framework in the <a href="http://dplyr.tidyverse.org/articles/programming.html">dplyr vignette</a>.</p>
</div>



<!-- dynamically load mathjax for compatibility with self-contained -->
<script>
  (function () {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src  = "https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
    document.getElementsByTagName("head")[0].appendChild(script);
  })();
</script>

</body>
</html>
