How to Display Line Numbers for Code Chunks in Rmarkdown HTML and PDF

How to display line numbers for code chunks in rmarkdown HTML and PDF

You can produce two code blocks: one for the presentation and another, hidden, for execution.

---
output:
pdf_document:
highlight: haddock
---

```{#numCode .R .numberLines}
x <- 1:10
y <- x^2
plot(x,y)
```

```{r results='asis', echo=FALSE}
x <- 1:10
y <- x^2
plot(x,y)
```

Note: If you replace pdf_document with html_document, you must provide the metadata "highlight".

How can I add line numbers that go across chunks in Rmarkdown?

As @mb21 pointed out in the comments, one can control the first line number of a code block by adding a startFrom attribute. However, this cannot be done manually, as knitr can split code blocks into multiple blocks, depending on content. We'll want to add this attribute programmatically.

The easiest method that I'm aware of is to let pandoc modify the structure, as all blocks have already been evaluated by the time pandoc sees them. We will use Lua instead of R for scripting, as that is the most efficient when working with pandoc filters.

The script will keep track of the number of code lines it has seen so far, and add the correct startFrom attribute to source code blocks. We can distinguish between source blocks and result blocks by checking for the numberLines class: only the former have that class.

-- Number of code lines seen so far.
local total_lines_count = 0

-- Count the number of newlines in a string.
function count_lines (text)
local count = 0
local last_pos = 0
repeat
last_pos = string.find(text, '\n', last_pos + 1, true)
count = count + 1
until not last_pos
return count
end

function CodeBlock (cb)
-- do nothing for result blocks
if not cb.classes:includes 'numberLines' then return nil end

cb.attributes['startFrom'] = total_lines_count + 1
total_lines_count = total_lines_count + count_lines(cb.text)
return cb
end

Now the only thing left is to tell pandoc to invoke the filter during conversion. That can be done by adding the --lua-filter option to pandoc_args:

---
output:
html_document:
highlight: kate
pandoc_args: ['--lua-filter=number-lines.lua']
---

The file number-lines.lua should contain the Lua code above and be placed in the same folder as your document, or in the filters folder below pandoc's data directory (see pandoc -v).

The advantage of this approach is that it works with HTML as well as PDF output.

Add line numbers to text content of a rendered rmarkdown html document

Interesting question and since I like to play around with JS and jQuery inside of RMarkdown documents I gave it a shot.

This solution is not bulletproof. It is only tested with Firefox. Since cross-browser compatibility of jQuery is a mess it will probably only work with Firefox.

Every line is now labeled including normal paragraphs, unordered and ordered lists, source code and output chunks.

Here is the complete working document:

---
title: "Title"
author: "Yourname"
date: "June 16, 2016"
output: html_document
runtime: shiny
---

<style>
/* Style the linenumber div */
.linenumbers {
border: 1px solid #ccc;
border-radius: 4px;
background-color: #EBEBEB;
text-align: center;
padding: 0px 3px;
font-family: monospace;
float: left;
position: absolute;
transform:translate(-125%);
font-size: inherit !important;
}

.main-container {
margin-left: 8% !important;
}

/* fixes the problem with inline code
that makes the line spacing bigger: */
p > code {
line-height: 90% !important;
}
</style>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>

<script>
$(document).ready(function(){
var $elements = $('p:not(:has(>img)), pre, ul, ol');
$elements.wrap('<numbering/>');
$('numbering').prepend('<div class=\"linenumbers\"/>');

updateLines = function(elmts) {
var counter = 1; // counts total number of lines
$(elmts).each(function() {

if($(this).is('pre')) {
var $elmt = $(this).children('code');
var styles = $(this).css([ "padding-top", "padding-bottom", "line-height"]);
$(this).siblings('.linenumbers').css(styles);
} else {
var $elmt = $(this);
}

var h = $elmt.outerHeight(); // get the height
var nl = Math.round(h /parseFloat($elmt.css('line-height')));
var text = '';
for(var i=counter; i < counter + nl; ++i) {
text += i + '</br>';
}
counter += nl;
$(this).siblings('.linenumbers').html(text);
});
};
updateLines($elements);

});

$(window).resize(function() {
updateLines($elements);
});
</script>

This R Markdown document has the ability to interactively number
the lines of text content. It might not be perfect but it sure
looks nice. If you find a bug or have a tip for how to further improve
the code, please let me know. I am no professional JavaScript
programmer so this was made by an amateur ;)

What if I leave some space here?

## Inputs and Outputs

Here starts another paragraph. Look how the line numbers jump over to
this one! This is amazing! For demonstrating purposes I have to write
some more lines. So I bore you a bit more with my nonsense. NONSENSE!
Ok let us try out some inline code and see whether the line numbers
still align. `library(ggplot2)` And a second `time()`. Looks ok I
guess.
Here starts another paragraph. Look how the line numbers jump over to
this one! This is amazing! For demonstrating purposes I have to write
some more lines.
So I bore you a bit more with my nonsense. NONSENSE! Ok let us try out
some inline code and see whether the line numbers still align.
`library(ggplot2)` And a second `time()`. Looks ok I guess.

```{r}
x <- 1:5
B <- 'Martin'
head(mtcars)
```

```{r}
plot(1)
```

You can exclude certain parts by just removing the corresponding tag from the line

var $elements = $('p:not(:has(>img)), pre,  ul, ol');

So if you do not want to label output chunks and lists, change it to

var $elements = $('p:not(:has(>img)), pre.r');

(Unlike output chunks, source code chunks carry the class .r.)

As I said for complex documents you might stumble over some bugs. Let me know if you do ;)

Sample Image

Putting line number for R code with knitr

This solution uses the LaTeX listings package to create line numbers. I can only get them to work by accumulating across all code chunks, but I imagine there is a similar solution that will enumerate lines only within each chunk. Here's the .Rnw source:

\documentclass{article}
\usepackage{listings}
\begin{document}

<<setup, echo=FALSE>>=
knit_hooks$set(source = function(x, options) {
paste("\\begin{lstlisting}[numbers=left, firstnumber=last]\n", x,
"\\end{lstlisting}\n", sep = "")
})
@

<<a, results='hold'>>=
1:2
3:4
5:6
@

<<b>>=
"test1"
"test2"
"test3"
@

\end{document}

The key parts of this are in the source hook, which is basically copied from here. The firstnumber=last tells listings to accumulate line numbers across listings. Without it, all lines are numbered 1 because knitr is putting each code line in its own listing.

And here's the result:

Sample Image

If you want each code block to start numbering from 1, add a hook to reset the counter:

knit_hooks$set(reset = function(before, options, envir){
if(before){
return("\\setcounter{lstnumber}{1}")
}
})

and then use reset=TRUE to activate the hook in each chunk you want:

<<a, results='hold', reset=TRUE>>=
1:2
3:4
@

Number each line of text in rmarkdown PDF

Add the lineno package to the header. Here is an example .Rmd file

---
title: "Line Number Example"
header-includes:
- \usepackage{lineno}
- \linenumbers
output:
pdf_document
---

There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.

There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.
There is a lot of text that goes here. There is a lot of text that goes here.

And the result looks like this:
Sample Image

R: Oxforddown numbers all my lines in pdf... What is the issue?

Numbered lines are very nice for reviewers. You don't want them in the final submitted version of the thesis, but it's nice for a reviewer to be able to say "You repeated the word Thanks on line 13", instead of making them count lines themselves.

To turn them on, keep

includeline-num: true

in your YAML. To turn them off, set this to false.

Create RMarkdown headers and code chunks in purrr

Solution

If you render your code as well as the results of running your code in a result='asis' chunk I think you can manage what you're after. You can do this by taking advantage of knitr's knit() function as follows:

---
title: "Untitled"
date: "10/9/2021"
output: html_document
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE)

full_var <- function(var) {

# Define the code to be run
my_code <- "print('test')"

# Print the code itself, surrounded by chunk formatting
cat("### `", var, "` {-} \n")
cat("```{r}", "\n")
cat(my_code, "\n")
cat("``` \n")

# Use knitr to render the results of running the code.
# NB, the use of Sys.time() here is to create unique chunk headers,
# which is required by knitr. You may want to reconsider this approach.
cat(knitr::knit(
text = sprintf("```{r %s}\n%s\n```\n", Sys.time(), my_code),
quiet = TRUE
))
}

vars <- c("1", "2", "3")
```

```{r results = "asis"}
purrr::walk(vars, full_var)
```

This produces output like the following:
Code output

Details: How knitr works

When knitr renders an R Markdown file it does so in the following stages:

  1. knitr generates a plain markdown file from your original .Rmd. This is when things like the yaml header and chunk options get used, and is crucially when your R code gets run
  2. pandoc converts the markdown file into the final output. Exactly what happens here depends on what output format you're using.

How knitr works

Using results='asis'

The chunk option results = 'asis' simply changes what the intermediate markdown script will look like in the rendering process. For example,

```{r}
cat("print('# Header')")
```

will be rendered to markdown as the following: (note that the indentation here means this is code according to markdown syntax):

    ## # print('# Header')

Whereas, if results = 'asis' is used you will get the markdown

print('# Header')

The crucial thing to realise is that, although "print('# Header')" is valid R code, it only appears at stage 2 in the process, which is after all R code has been run.

The take-home message

Unfortunately, you can't expect results='asis' to output R code and then run it, as knitr has already finished running your R code by this point.



Related Topics



Leave a reply



Submit