├── .github
├── .gitignore
└── FUNDING.yml
├── cv_data.xlsx
├── .gitignore
├── examples
├── formats.png
├── cv-2-column.jpg
├── cv-2-column.pdf
├── cv-academic.jpg
├── cv-academic.pdf
└── cv-website.jpg
├── .Rbuildignore
├── pagedownCV.Rproj
├── R
├── save_gsheet_as_excel_workbook.R
└── utility-functions.R
├── css
├── website-cv.css
├── two-col-cv.css
└── academic_cv.css
├── README.md
├── cv-2-column.Rmd
├── cv-website.Rmd
└── cv-academic.Rmd
/.github/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 |
--------------------------------------------------------------------------------
/cv_data.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulyngs/pagedownCV/HEAD/cv_data.xlsx
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .Rproj.user
2 | .Rhistory
3 | .RData
4 | .Ruserdata
5 | .DS_Store
6 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: ulyngs
4 |
--------------------------------------------------------------------------------
/examples/formats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulyngs/pagedownCV/HEAD/examples/formats.png
--------------------------------------------------------------------------------
/examples/cv-2-column.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulyngs/pagedownCV/HEAD/examples/cv-2-column.jpg
--------------------------------------------------------------------------------
/examples/cv-2-column.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulyngs/pagedownCV/HEAD/examples/cv-2-column.pdf
--------------------------------------------------------------------------------
/examples/cv-academic.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulyngs/pagedownCV/HEAD/examples/cv-academic.jpg
--------------------------------------------------------------------------------
/examples/cv-academic.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulyngs/pagedownCV/HEAD/examples/cv-academic.pdf
--------------------------------------------------------------------------------
/examples/cv-website.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulyngs/pagedownCV/HEAD/examples/cv-website.jpg
--------------------------------------------------------------------------------
/.Rbuildignore:
--------------------------------------------------------------------------------
1 | ^.*\.Rproj$
2 | ^\.Rproj\.user$
3 | ^_pkgdown\.yml$
4 | ^docs$
5 | ^pkgdown$
6 | ^\.github$
7 | ^README\.Rmd$
8 |
--------------------------------------------------------------------------------
/pagedownCV.Rproj:
--------------------------------------------------------------------------------
1 | Version: 1.0
2 |
3 | RestoreWorkspace: Default
4 | SaveWorkspace: Default
5 | AlwaysSaveHistory: Default
6 |
7 | EnableCodeIndexing: Yes
8 | UseSpacesForTab: Yes
9 | NumSpacesForTab: 2
10 | Encoding: UTF-8
11 |
12 | RnwWeave: Sweave
13 | LaTeX: pdfLaTeX
14 |
15 | AutoAppendNewline: Yes
16 | StripTrailingWhitespace: Yes
17 |
18 | BuildType: Package
19 | PackageUseDevtools: Yes
20 | PackageInstallArgs: --no-multiarch --with-keep.source
21 |
--------------------------------------------------------------------------------
/R/save_gsheet_as_excel_workbook.R:
--------------------------------------------------------------------------------
1 | #' save my google sheet as an excel file
2 | library(openxlsx)
3 | library(googlesheets4)
4 | gs4_deauth()
5 |
6 | # the link to my CV on google sheets
7 | my_cv_url <- "https://docs.google.com/spreadsheets/d/1ta71CAGkcLqm-W1UdVRA_JJSddWV2TsrRZsCnQlmOis/edit?usp=sharing"
8 |
9 | # read in the two tabs we want
10 | cv_entries <- read_sheet(my_cv_url, sheet = "cv_entries")
11 | publications <- read_sheet(my_cv_url, sheet = "publications")
12 |
13 | # write it to an excel file
14 | write.xlsx(list("cv_entries" = cv_entries, "publications" = publications), "cv_data.xlsx")
15 |
16 |
--------------------------------------------------------------------------------
/css/website-cv.css:
--------------------------------------------------------------------------------
1 | thead {
2 | display: none;
3 | }
4 |
5 | table.dataTable.no-footer {
6 | border-bottom: none !important;
7 | }
8 |
9 | .cv-entries tbody td {
10 | border-bottom: none !important;
11 | border-top: none !important;
12 | padding: 0.2em 0em 0.4em 0.2em;
13 | font-size: 1em;
14 | }
15 |
16 | .cv-entry-title {
17 | font-weight: bold;
18 | }
19 |
20 | .tab-content .tab-pane {
21 | padding: 0;
22 | margin: 0;
23 | }
24 |
25 | .tab-content label {
26 | padding: 1em 0em 0.8em 0.2em;
27 | }
28 | .tab-content .tab-pane td {
29 | padding: 0.2em 0em 0.4em 0.2em;
30 | }
31 | .tab-content table {
32 | margin-top: 1em;
33 | margin-bottom: 1.5em;
34 | }
35 |
36 | .tab-content #talks table,
37 | .tab-content #posters table {
38 | margin-bottom: 0.3em;
39 | }
40 |
41 | .tab-content #talks p,
42 | .tab-content #posters p {
43 | margin-bottom: 1px;
44 | }
45 |
46 |
47 |
48 | h1 {
49 | font-size: 2.2em;
50 | }
51 | h2 {
52 | font-size: 1.7em;
53 | margin-bottom: 0.5em;
54 | margin-top: 1em;
55 | }
56 | h3 {
57 | font-size: 1.3em;
58 | }
59 | .domain-header {
60 | margin-top: 2.5em;
61 | }
62 | thead {
63 | display: none !important;
64 | }
65 | table{
66 | margin-bottom: 3em;
67 | table-layout: fixed;
68 | width: 100%;
69 | }
70 | .datatables.html-widget {
71 | margin-bottom: 3em !important;
72 | }
73 | tbody td:first-child {
74 | padding-right: 1.5em;
75 | padding-left: 0;
76 | vertical-align: top;
77 | width: 18%;
78 | }
79 |
80 | /* the talks table has three columns */
81 | #talks-table-dt tbody td:nth-child(1),
82 | #talks-table-dt tbody td:nth-child(2) {
83 | width: 9%;
84 | vertical-align: top;
85 | }
86 | #talks-table-dt tbody td:nth-child(3) {
87 | width: 82%;
88 | }
89 |
90 | td {
91 | padding-bottom: 0.2em;
92 | }
93 | .work-title {
94 | font-size: 1.2em;
95 | }
96 | .datatables {
97 | margin-bottom: 2em;
98 | }
99 |
100 | .datatables .paginate_button.current {
101 | border: none !important;
102 | background: none !important;
103 | font-weight: bold;
104 | }
105 | .datatables a.paginate_button:hover {
106 | background: rgba(0,0,0,.04) !important;
107 | color: black !important;
108 | border: none !important;
109 | }
110 | .datatables .dataTables_length select {
111 | background-color: #fff;
112 | padding: 3px;
113 | border-radius: 3px;
114 | border: 1px solid rgba(0,0,0,.05);
115 | }
116 |
117 | .talk-translation {
118 | font-style: italic;
119 | }
120 |
121 | /**** HYPERLINKS ******/
122 | /* by default black, underlined and no line on hover */
123 | a {
124 | color: black;
125 | border-bottom: 1px solid lightgray;
126 | }
127 | a:hover {
128 | text-decoration: none;
129 | color: black;
130 | border-bottom: none;
131 | }
132 |
--------------------------------------------------------------------------------
/R/utility-functions.R:
--------------------------------------------------------------------------------
1 | # add a column 'number' that counts entries in order of date -- adds leading zero, and optionally a letter
2 | add_leading_zeroes_and_letter <- function(cv_entries_tibble, letter_to_pad = ""){
3 | if(nrow(cv_entries_tibble) > 9){
4 | cv_entries_tibble |>
5 | dplyr::arrange(date) |>
6 | dplyr::mutate(number = stringr::str_pad(row_number(), width = 2, side = "left", pad = "0"),
7 | number = stringr::str_c(letter_to_pad, number)) |>
8 | dplyr::arrange(desc(date))
9 | } else {
10 | cv_entries_tibble |>
11 | dplyr::arrange(date) |>
12 | dplyr::mutate(number = stringr::str_pad(row_number(), width = 1, side = "left", pad = "0"),
13 | number = stringr::str_c(letter_to_pad, number)) |>
14 | dplyr::arrange(desc(date))
15 | }
16 | }
17 |
18 |
19 | # Turn year into an empty string when it's the same year as the previous entry
20 | blank_year_when_repeated <- function(cv_entry_tibble){
21 | cv_entry_tibble |>
22 | dplyr::mutate(row_number = dplyr::row_number()) |>
23 | dplyr::mutate(year = as.character(year),
24 | year = dplyr::case_when(
25 | row_number == 1 ~ year,
26 | year == dplyr::lag(year) ~ "",
27 | year != dplyr::lag(year) ~ year
28 | )) %>%
29 | select(-row_number)
30 | }
31 |
32 |
33 | # Adds a page break after a row in a column that contains a specified text
34 | # publications |>
35 | # manual_page_break_after_row("title", "Before and after GDPR")
36 | manual_page_break_after_row <- function(some_tibble, a_column_name, text_to_detect, use_glue = TRUE){
37 | if(use_glue){
38 | some_tibble |>
39 | dplyr::mutate({{a_column_name}} := if_else(stringr::str_detect(.data[[a_column_name]], text_to_detect),
40 | glue::glue("{.data[[a_column_name]]}
"),
41 | glue::glue("{.data[[a_column_name]]}")
42 | ))
43 | } else {
44 | some_tibble |>
45 | dplyr::mutate({{a_column_name}} := if_else(stringr::str_detect(.data[[a_column_name]], text_to_detect),
46 | paste(.data[[a_column_name]], "
"),
47 | paste(.data[[a_column_name]])
48 | ))
49 | }
50 | }
51 |
52 |
53 | #' Take an author name and replace the first name with an initial and dot
54 | #' e.g. When given "Ulrik Lyngs" it returns "U. Lyngs"
55 | replace_first_name_with_initial_and_dot <- function(author_name) {
56 | stringr::str_replace(author_name, "(?<=\\S)\\S+", "\\.")
57 | }
58 |
59 |
60 | #' Take a comma-separated string of authors and replace the first names with an initial and a dot
61 | # When given "Ulrik Lyngs, Kai Lukoff" it returns "U. Lyngs, K. Lukoff"
62 | replace_first_names_in_author_list_with_initial_and_dot <- function(authors){
63 | authors_split <- stringr::str_split(authors, ",") %>%
64 | map(stringr::str_trim)
65 |
66 | authors_split[[1]] %>% purrr::map_chr(replace_first_name_with_initial_and_dot) %>%
67 | paste0(collapse = ", ")
68 | }
69 |
--------------------------------------------------------------------------------
/css/two-col-cv.css:
--------------------------------------------------------------------------------
1 | /* THIS CREATES BASIC PAGED LAYOUT IN COMBINATION WITH PAGED.HTML */
2 | @page{
3 | size: A4 portrait;
4 | }
5 |
6 | body {
7 | font-family: "Latin Modern Roman", "Book Antiqua", Palatino, serif
8 | }
9 |
10 | :root{
11 | --page-width: 8.5in;
12 | --pagedjs-margin-right: 0.6in;
13 | --pagedjs-margin-left: 0.6in;
14 | --pagedjs-margin-top: 0.4in;
15 | --pagedjs-margin-bottom: 0.3in;
16 | --root-font-size: 12pt;
17 | --viewer-pages-spacing: 12px;
18 | --viewer-shadow-color: #313131; /* this marks the pages */
19 | }
20 |
21 |
22 | /* Paged.js viewer */
23 | @media screen {
24 | body {
25 | background-color: var(--viewer-background-color);
26 | margin: 0; /* for mobile */
27 | width: calc(var(--pagedjs-width) + 2 * var(--viewer-pages-spacing)); /* for mobile */
28 | }
29 | .pagedjs_pages {
30 | max-width: var(--pagedjs-width);
31 | margin: 0 auto;
32 | display: flex;
33 | flex-direction: column;
34 | }
35 | .pagedjs_page {
36 | box-shadow: 0 0 calc(0.66667 * var(--viewer-pages-spacing)) var(--viewer-shadow-color);
37 | margin: var(--viewer-pages-spacing) 0;
38 | }
39 | }
40 | @media screen and (min-width: 8.5in) {
41 | /* not a mobile */
42 | body {
43 | margin: auto;
44 | width: unset;
45 | }
46 | }
47 |
48 | /*
49 | if we get the second page started already on the first page
50 | then make sure we ain't got a border showing up!
51 | */
52 | .pagedjs_first_page .pagetwo h1 {
53 | border-bottom: none !important;
54 | }
55 |
56 |
57 | /* create ability to insert pagebreaks with br.pagebreak */
58 | br.pageBreak {
59 | page-break-after: always;
60 | }
61 |
62 | p, li {
63 | font-size: var(--root-font-size);
64 | line-height: 115%;
65 | }
66 |
67 | a {
68 | text-decoration: none;
69 | color: black;
70 | }
71 |
72 | /* title */
73 | h1.title {
74 | text-align: center;
75 | font-weight: normal;
76 | font-size: 2.3em;
77 | line-height: 110%;
78 | }
79 |
80 | /* section headers */
81 | .row h1 {
82 | color: #710C0C;
83 | border-bottom: 1px solid black;
84 | margin-top: 0.5em;
85 | margin-bottom: 0.2em;
86 | font-size: 1.5em;
87 | font-weight: normal;
88 | }
89 |
90 |
91 | /* PAGE 1's summary and info box */
92 | h1.box {
93 | margin-top: 0.1em;
94 | margin-bottom: 0.4em;
95 | }
96 | .summary p {
97 | margin-top: 0.2em;
98 | margin-bottom: 0.5em;
99 | }
100 |
101 | .info-box {
102 | background-color: #F5DD9E;
103 | padding: 14px 14px;
104 | margin: 0.2em 0 0 0;
105 | line-height: 125%;
106 | border-radius: 2px;
107 | font-size: 0.9em;
108 | }
109 | .info-box thead {
110 | display: none;
111 | }
112 | .info-box col:first-child{
113 | width: 12%;
114 | text-align: center;
115 | }
116 | .info-box a {
117 | text-decoration: none;
118 | color: black;
119 | }
120 |
121 | /* style content tables */
122 | .col-6 thead {
123 | display: none;
124 | }
125 |
126 | /* set the width of the first column in the tables */
127 | .col-6 td:first-child {
128 | width: 18%;
129 | }
130 |
131 | .col-6 td {
132 | font-size: 0.95em;
133 | vertical-align: top;
134 | line-height: 120%;
135 | padding: 3px 0;
136 | }
137 |
138 | td {
139 | vertical-align: top;
140 | text-align: left;
141 | }
142 |
143 | .cv-entry-title {
144 | font-weight: bold;
145 | }
146 |
147 |
148 | /* if you use links to footnotes*/
149 | .footnote-ref sup {
150 | vertical-align: top;
151 | margin-left: 2px;
152 | }
153 |
154 | /*links at the bottom */
155 | .footnotes p {
156 | margin-block-start: 0.8em;
157 | margin-block-end: 0.8em;
158 | line-height: 0.9;
159 | }
160 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # `pagedownCV`
2 |
3 | This repo provides three R Markdown templates for creating a
4 | dynamically generated CV using `R Markdown`. The templates use the
5 | [`pagedown`](https://github.com/rstudio/pagedown) library for the paged versions.
6 |
7 | ## How to use
8 | First, download or clone this repo.
9 |
10 | ### Knitting
11 | Open and knit one of the .Rmd files:
12 |
13 | - [**cv-2-column.Rmd**](https://ulyngs.github.io/pagedownCV/examples/cv-2-column.pdf) (creates a PDF when knit) \
14 |
15 |
16 | - [**cv-academic.Rmd**](https://ulyngs.github.io/pagedownCV/examples/cv-academic.pdf) (creates a PDF when knit; traditional one-column format, appropriate for a traditionally laid out academic CV) \
17 |
18 |
19 | - [**cv-website.Rmd**](https://ulyngs.github.io/pagedownCV/examples/cv-website.html) (creates an HTML file when knit; example of a CV laid out as it might be used on a website) \
20 |
21 |
22 | ## Fiddling with page breaks
23 |
24 | In both the Academic CV and the 2-column CV, you can write
25 | `
` in your R Markdown source file to
26 | add a page break at this location.
27 |
28 | In the Academic CV, cell content that’s overflowing
29 | to the next page might get placed in the wrong column on the subsequent page
30 | (I’ve flagged it in [this
31 | issue](https://github.com/rstudio/pagedown/issues/299) for `pagedown` —
32 | hopefully there’s a solution soon). If you encounter this, you can work
33 | around it by manually inserting a page break in the row before. There’s
34 | a convenience function for this:
35 |
36 | ```r
37 | # Adds a page break after a row that contains a specified text in a specified column
38 | a_data_frame |>
39 | manual_page_break_after_row("name-of-a-column", "Text in that column")
40 | ```
41 |
42 | If you look through **cv-academic.Rmd** then you'll see examples of this throughout.
43 |
44 | Similarly, you'll find convenience functions for replacing authors' first names with an initial followed by a dot, and for replacing a repeated year with an empty string.
45 |
46 | The convenience functions live in **R/utility-functions.R**
47 |
48 | ## Motivation
49 |
50 | The [`pagedown`](https://github.com/rstudio/pagedown) package lets you
51 | output an R Markdown file to paged HTML content which can then be saved
52 | as PDF. This means you can use R Markdown to programmatically pull out
53 | content from some spreadsheet with our CV data, output it to a paged
54 | format, do the styling with CSS, then save as PDF and/or host it online.
55 | Brilliant!
56 |
57 | Nick Strayer’s excellent [data-driven
58 | cv](http://nickstrayer.me/datadrivencv/)) package, as well as the base
59 | format for resumés provided by `pagedown` (`pagedown::html_resume`),
60 | make this easy to do.
61 |
62 | However, their available layouts don’tsuit my taste (especially for an
63 | academic CV).
64 |
65 | ## Where do the layouts come from?
66 |
67 | When I designed the Academic CV, I was particularly inspired by the
68 | resumés of [Matthew Kay](http://www.mjskay.com/) and [Elena
69 | Agapie](https://eagapie.com/pubs/cv.pdf). I use this format for academic
70 | purposes.
71 |
72 | The two-column one, I originally designed in Microsoft Word, with the
73 | intention of using it for brief, professional 2-page resumés where you
74 | need to squeeze content into less space.
75 |
76 | ## How does it work?
77 |
78 | Each resumé is generated by an R Markdown file that pulls in content
79 | from the included excel file **cv_data.xlsx**.
80 |
81 | The sample content comes from [this google
82 | sheet](https://docs.google.com/spreadsheets/d/1ta71CAGkcLqm-W1UdVRA_JJSddWV2TsrRZsCnQlmOis/edit?usp=sharing),
83 | which holds my CV information along with most of the content for [my
84 | website](https://ulriklyngs.com/).
85 |
86 | Enjoy!!
87 |
--------------------------------------------------------------------------------
/css/academic_cv.css:
--------------------------------------------------------------------------------
1 | /* THIS CREATES BASIC PAGED LAYOUT IN COMBINATION WITH PAGED.HTML */
2 | @page{
3 | size: A4 portrait;
4 | }
5 |
6 | :root{
7 | --page-width: 8.5in;
8 | --pagedjs-margin-right: 0.7in;
9 | --pagedjs-margin-left: 0.7in;
10 | --pagedjs-margin-top: 0.5in;
11 | --pagedjs-margin-bottom: 0.5in;
12 | --root-font-size: 11pt;
13 | --viewer-pages-spacing: 12px;
14 | --viewer-shadow-color: #313131; /* this marks the pages */
15 | --my-darkgray: #929598;
16 | --indentation: 18%;
17 | }
18 |
19 | /* Paged.js viewer */
20 | @media screen {
21 | body {
22 | background-color: var(--viewer-background-color);
23 | margin: 0; /* for mobile */
24 | width: calc(var(--pagedjs-width) + 2 * var(--viewer-pages-spacing)); /* for mobile */
25 | }
26 | .pagedjs_pages {
27 | max-width: var(--pagedjs-width);
28 | margin: 0 auto;
29 | display: flex;
30 | flex-direction: column;
31 | }
32 | .pagedjs_page {
33 | box-shadow: 0 0 calc(0.66667 * var(--viewer-pages-spacing)) var(--viewer-shadow-color);
34 | margin: var(--viewer-pages-spacing) 0;
35 | }
36 | }
37 | @media screen and (min-width: 8.5in) {
38 | /* not a mobile */
39 | body {
40 | margin: auto;
41 | width: unset;
42 | }
43 | }
44 |
45 | /* create ability to insert pagebreaks with br.pagebreak */
46 | br.pageBreak {
47 | page-break-after: always;
48 | }
49 |
50 | body {
51 | font-family: "Avenir";
52 | }
53 |
54 | p, li {
55 | font-size: var(--root-font-size);
56 | line-height: 125%;
57 | }
58 |
59 | /* section headers */
60 | h2 {
61 | margin-top: 0.6em;
62 | margin-bottom: 0.4em;
63 | font-size: 1.5em;
64 | font-weight: bold;
65 | margin-left: var(--indentation);
66 | }
67 | h3 {
68 | margin-left: var(--indentation);
69 | margin-bottom: 0.6em;
70 | }
71 | .conference-presentations-omitted {
72 | margin-left: var(--indentation);
73 | font-style: italic;
74 | }
75 |
76 | /****** Page numbers ******/
77 | @page {
78 | @top-left {
79 | content: none;
80 | }
81 | @top-right {
82 | content: none;
83 | }
84 | @bottom-right {
85 | content: counter(page) var(--last-updated);
86 | }
87 | }
88 |
89 | .pagedjs_margin-bottom-right {
90 | text-align: left !important;
91 | margin-left: var(--indentation);
92 | color: var(--my-darkgray);
93 | font-size: 0.8em;
94 | margin-bottom: 20px;
95 | }
96 |
97 | /***** STYLE HEADING *******/
98 | .title-and-contact {
99 | display: grid;
100 | grid-template-columns: 70% 30%;
101 | border-style: none none groove none;
102 | margin-bottom: 1.8em;
103 | padding-bottom: 0.5em;
104 | }
105 |
106 | .title h1 {
107 | text-align: left;
108 | font-size: 2.5em;
109 | padding: 0;
110 | margin: 0% 0% 0% 24.5%;
111 | }
112 |
113 | .contact-info a {
114 | color: black;
115 | text-decoration: none;
116 | }
117 | .contact-info {
118 | font-size: 0.5em;
119 | text-align: right;
120 | }
121 |
122 | /***** STYLE INTERESTS STATEMENT *******/
123 | /* research interests */
124 | .research-interests {
125 | display: grid;
126 | grid-template-columns: 18.5% 81.5%;
127 | }
128 | .interests p {
129 | line-height: 135%;
130 | }
131 | .interests a {
132 | text-decoration: none;
133 | }
134 |
135 |
136 | /***** STYLE CV ENTRIES *******/
137 | /* overall styling for cv-entries within a table */
138 | .cv-entries table {
139 | border-spacing: 0;
140 | margin: 0.1em 0 1.5em 0;
141 | width: 100%;
142 | }
143 |
144 | .cv-entries thead {
145 | display: none;
146 | }
147 |
148 | .cv-entries td {
149 | vertical-align: top;
150 | line-height: 125%;
151 | padding-bottom: 0.5em;
152 | font-size: var(--root-font-size);
153 | }
154 |
155 | /* make titles bold */
156 | .cv-entry-title {
157 | font-weight: bold;
158 | }
159 |
160 | .cv-entries a,
161 | .footnotes a {
162 | color: #005EA3;
163 | }
164 |
165 | /* footnote links */
166 | a.footnote-ref {
167 | text-decoration: none;
168 | }
169 |
170 | .footnote-ref sup {
171 | vertical-align: top;
172 | margin-left: 1px;
173 | font-weight: normal;
174 | }
175 |
176 | /* basic styling for first column in two-column cv entries */
177 | .cv-entries td:first-child {
178 | width: var(--indentation);
179 | text-align: left;
180 | color: var(--my-darkgray);
181 | font-size: 10pt;
182 | }
183 |
184 | /* basic styling for two first columns in three-column cv entries */
185 | .cv-entries .three-split td:first-child {
186 | width: 9%;
187 | text-align: left;
188 | color: var(--my-darkgray);
189 | }
190 | .cv-entries .three-split td:nth-child(2) {
191 | width: 9%;
192 | text-align: left;
193 | color: var(--my-darkgray);
194 | font-size: 10pt;
195 | }
196 |
197 | /*** style the publication entries ***/
198 | /* set width for the publication info statement */
199 | .grid-container-publications {
200 | display: grid;
201 | grid-template-columns: 49% 51%;
202 | }
203 | .conference-publication-heading h3 {
204 | margin-block-start: 0 !important;
205 | margin-top: 15px !important;
206 | margin-left: 37%;
207 | }
208 | .conference-note h3 {
209 | margin-block-start: 0 !important;
210 | margin-top: 21px !important;
211 | color: var(--my-darkgray);
212 | font-size: 0.8em;
213 | }
214 |
215 | .review-note {
216 | color: var(--my-darkgray);
217 | font-size: 0.8em;
218 | }
219 |
220 | /* make award texts red */
221 | .publication-award {
222 | color: #b8162b;
223 | }
224 |
225 |
226 | /* don't show a horizontal rule before the final links */
227 | hr {
228 | display: none;
229 | }
230 |
231 | span.talk-title {
232 | padding-top: 0.2em;
233 | }
234 |
235 | .cv-entry-translation {
236 | font-style: italic;
237 | }
238 |
--------------------------------------------------------------------------------
/cv-2-column.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Ulrik Lyngs
Curriculum vitae"
3 | output:
4 | pagedown::html_paged:
5 | css: ["css/bootstrap-5.2.1.min.css", "css/two-col-cv.css"]
6 | number_sections: false
7 | knit: pagedown::chrome_print
8 | ---
9 |
10 | ```{r setup, include=FALSE}
11 | knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE)
12 | library(tidyverse)
13 | library(knitr)
14 | library(glue)
15 | library(fontawesome)
16 | library(readxl)
17 | library(lubridate)
18 | source("R/utility-functions.R")
19 |
20 | cv_entries <- read_excel("cv_data.xlsx", sheet = "cv_entries") |>
21 | mutate(year = year(date))
22 | publications <- read_excel("cv_data.xlsx", sheet = "publications") |>
23 | mutate(year = year(date))
24 | ```
25 |
26 |
27 | ::: {.row}
28 |
29 | :::::: {.col-8 .summary}
30 |
31 | # Research summary
32 | **Aims**---to develop and evaluate design patterns that help people self-regulate their use of smartphones and laptops.\
33 | **Methods**---controlled studies, user surveys, interviews, co-design workshops, web scraping, behaviour logging.
34 |
35 | **I am passionate about** open and transparent research. Since 2018, I have shared materials, analysis scripts, and data for all my first-authored work, and written my papers as reproducible documents in [R Markdown](https://rmarkdown.rstudio.com).
36 |
37 | ::::::
38 |
39 | :::::: {.col-4}
40 |
41 | # Contact {.box}
42 |
43 | ::::::::: {.info-box}
44 |
45 | | Center | Left |
46 | |:------:|:-----|
47 | | `r fa("map-marker-alt")`| Dept. of Computer Science, University of Oxford |
48 | | `r fa("envelope")` | ulrik.lyngs@cs.ox.ac.uk |
49 | | `r fa("globe")` | [ulriklyngs.com](https://ulriklyngs.com) |
50 | | `r fa("github")` | [ulyngs](https://github.com/ulyngs) |
51 |
52 | :::::::::
53 |
54 | ::::::
55 |
56 | :::
57 |
58 | ::: {.row}
59 |
60 | :::::: {.col-6 .left}
61 |
62 | # Awards and honours
63 |
64 | ```{r}
65 | cv_entries |>
66 | filter(type == 'awards', short_cv == "y") |>
67 | mutate(what = glue("{what}, {where}")) |>
68 | select(year, what) |>
69 | kable()
70 | ```
71 |
72 | # Selected major grants
73 |
74 | ```{r}
75 | cv_entries |>
76 | filter(type == 'major_grants', short_cv == "y") |>
77 | mutate(what = if_else(is.na(additional_info),
78 | glue("{what} ({additional_info2}), {where}"),
79 | glue("{what} ({additional_info2}), {where}"))) |>
80 | select(year, what) |>
81 | kable()
82 | ```
83 |
84 | ::::::
85 |
86 | :::::: {.col-6 .right}
87 |
88 | # Education
89 |
90 | ```{r}
91 | cv_entries |>
92 | filter(type == 'education') |>
93 | mutate(what = glue("{what}, {where}")) |>
94 | select(year, what) |>
95 | kable()
96 | ```
97 |
98 | # Selected teaching experience
99 | ```{r}
100 | cv_entries |>
101 | filter(type == 'teaching', short_cv == "y") |>
102 | mutate(what = glue("{what}, {where}
",
103 | "{additional_info}")) |>
104 | select(year, what) |>
105 | kable()
106 | ```
107 |
108 | ::::::
109 |
110 | :::
111 |
112 |
113 |
114 |
115 | ::: {.row .pagetwo}
116 |
117 | :::::: {.col-6 .left}
118 |
119 | # Selected talks {.talks}
120 | ```{r}
121 | cv_entries |>
122 | filter(type == 'talk', short_cv == "y") |>
123 | # add commas as appropriate
124 | mutate(where = if_else(!is.na(where) & !is.na(institution), glue(", {where}"), where),
125 | department = if_else(!is.na(department), glue(", {department}"), department),
126 | additional_info = if_else(!is.na(additional_info), glue(", {additional_info}"), "")) |>
127 | # add slide and video links
128 | mutate(slides = if_else(!is.na(slides), glue("Slides"), ""),
129 | video = if_else(!is.na(video), glue("Video"), "")) |>
130 | # put it all together
131 | mutate(what = glue("{institution}{where}{department}{additional_info}
",
132 | "{what}",
133 | .na = "")) |>
134 | select(year, what) |>
135 | kable()
136 | ```
137 |
138 | ::::::
139 |
140 | :::::: {.col-6 .right}
141 |
142 | # Selected media & panels
143 | ```{r}
144 | cv_entries |>
145 | filter(type == 'media' | type == 'talk-podcast' | type == 'talk-panel') |>
146 | filter(short_cv == "y") |>
147 | # format the translation
148 | mutate(what_translation = if_else(!is.na(what_translation), glue("({what_translation})"), what_translation)) |>
149 | replace_na(list(what_translation = "")) |>
150 | # put it all together
151 | mutate(what = glue("{where}, {what} *{what_translation}*")) |>
152 | select(year, what) |>
153 | kable()
154 | ```
155 |
156 | # Professional development {.prof-dev}
157 | ```{r}
158 | cv_entries |>
159 | filter(type == 'prof-dev') |>
160 | mutate(what = glue("{what}, {where}")) |>
161 | select(year, what) |>
162 | kable()
163 | ```
164 |
165 | # Selected service
166 | ```{r}
167 | cv_entries |>
168 | filter(type == 'service', short_cv == "y") |>
169 | mutate(where = if_else(!is.na(url), glue("[{where}]({url})"), where)) |>
170 | mutate(what = glue("{what} {where}")) |>
171 | arrange(desc(date_end), desc(date)) |>
172 | select(year, what) |>
173 | kable()
174 | ```
175 |
176 | ::::::
177 | :::
178 |
--------------------------------------------------------------------------------
/cv-website.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | output:
3 | html_document:
4 | css: css/website-cv.css
5 | toc: true
6 | toc_float: true
7 | ---
8 |
9 | ```{r setup, include=FALSE}
10 | knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE)
11 | library(tidyverse)
12 | library(knitr)
13 | library(glue)
14 | library(fontawesome)
15 | library(DT)
16 | library(lubridate)
17 | library(readxl)
18 | source("R/utility-functions.R")
19 |
20 | cv_entries <- read_excel("cv_data.xlsx", sheet = "cv_entries") |>
21 | mutate(year = year(date))
22 | publications <- read_excel("cv_data.xlsx", sheet = "publications") |>
23 | mutate(year = year(date))
24 | ```
25 |
26 | # Summary
27 |
28 | I am passionate about using insights from the behavioural neurosciences to **design digital technology that is sensitive to human limitations and biases**, particularly in relation to attention and self-regulation.
29 |
30 | My **academic and professional background** is highly interdisciplinary, and I try to integrate best practices from many work spheres, such as using tools from large-scale event management to structure academic projects.
31 |
32 | My **personal interests** include backpacking, samatha meditation, dancing (Cuban salsa and swing), surfing, and playing music (often with my concept band the [Karaoke Collective](https://karaokecollective.com)).
33 |
34 | [`r fa("download")` CV as PDF](https://ulriklyngs.com/pdfs/academic_cv.pdf)
35 |
36 | # Academia {.domain-header}
37 | ## Research positions
38 | ```{r}
39 | cv_entries |>
40 | filter(type == 'research_positions', is.na(exclude)) |>
41 | mutate(what = str_c("", what, "
",
42 | where)) |>
43 | select(year, what) |>
44 | kable(escape = FALSE)
45 | ```
46 |
47 | ## Education
48 | ```{r}
49 | cv_entries |>
50 | filter(type == 'education') |>
51 | mutate(what = str_c("", what, ", ", where,
52 | "
", additional_info,
53 | "
", additional_info2)) |>
54 | select(year, what) |>
55 | kable(escape = FALSE)
56 |
57 | ```
58 |
59 | ## Awards and honours
60 | ```{r}
61 | cv_entries |>
62 | filter(type == 'awards') |>
63 | filter(!(website == "n") | is.na(website)) |>
64 | mutate(what = if_else(
65 | !is.na(where),
66 | str_c("", what, ", ", where),
67 | str_c("", what, ""))
68 | ) |>
69 | blank_year_when_repeated() |>
70 | select(year, what) |>
71 | kable(align = c("l", "l"), escape = FALSE)
72 | ```
73 |
74 | ## Grants and funding
75 | ```{r}
76 | cv_entries |>
77 | filter(type == 'minor_grants' | type == 'major_grants',
78 | is.na(exclude)) |>
79 | filter(!(website == "n") | is.na(website)) |>
80 | mutate(what = if_else(
81 | !is.na(additional_info),
82 | str_c("", what, ", ", where, "
", additional_info),
83 | str_c("", what, ", ", where))
84 | ) |>
85 | arrange(date_end) |>
86 | blank_year_when_repeated() |>
87 | select(year, what) |>
88 | kable(align = c("l", "l"), escape = FALSE)
89 | ```
90 |
91 |
92 | ## Research dissemination {.tabset .output-header}
93 | ### Talks
94 | ```{r}
95 | cv_entries |>
96 | filter(type == 'talk') |>
97 | mutate(where = str_replace(where, "\\[([^]]*)\\]\\(([^\\s^\\)]*)[\\s\\)]", "\\1")) |>
98 | mutate(what = glue::glue("{what}, {where}", .na = "")) |>
99 | select(date, what) |>
100 | arrange(desc(date)) |>
101 | mutate(date = str_c(year(date),
102 | ", ",
103 | month(date, label = TRUE))) |>
104 | datatable(rownames = FALSE, escape = FALSE, class = 'row-border', options = list(
105 | pageLength = 10
106 | ))
107 |
108 | ```
109 |
110 | ### Posters {.posters}
111 | ```{r}
112 | cv_entries |>
113 | filter(type == 'poster') |>
114 | mutate(what = str_c("*", what, "*, ", where)) |>
115 | select(year, what) |>
116 | arrange(desc(year)) |>
117 | kable()
118 | ```
119 |
120 | ### Media {.media}
121 | ```{r}
122 | cv_entries |>
123 | filter(type == 'media') |>
124 | select(year, what) |>
125 | kable()
126 | ```
127 |
128 |
129 |
130 | ## Teaching experience
131 |
132 | ```{r}
133 | cv_entries |>
134 | filter(type == 'teaching') |>
135 | mutate(what = ifelse(!is.na(additional_info),
136 | str_c("", what, "
", where, "
", additional_info),
137 | str_c("", what, "
", where))) |>
138 | select(year, what) |>
139 | kable(escape = FALSE)
140 |
141 | ```
142 |
143 | ## Service
144 | ```{r}
145 | cv_entries |>
146 | filter(type == 'service') |>
147 | mutate(where = ifelse(!is.na(url), str_c("[", where, "](", url, ")"), where)) |>
148 | mutate(what = ifelse(!is.na(additional_info),
149 | str_c("", what,
150 | "
",
151 | where),
152 | str_c("", what,
153 | "
",
154 | where))) |>
155 | arrange(desc(date_end), desc(date)) |>
156 | select(year, what) |>
157 | kable(escape = FALSE)
158 |
159 | ```
160 |
161 |
162 | # Work
163 | ## Selected employments
164 | ```{r}
165 | cv_entries |>
166 | filter(type == 'work', is.na(website)) |>
167 | mutate(additional_info = replace_na(additional_info, "")) |>
168 | mutate(what = str_c("", what, "
", where, "
", additional_info)) |>
169 | arrange(desc(date)) |>
170 | select(year, what) |>
171 | kable(escape = FALSE)
172 | ```
173 |
174 | ## Professional development
175 | ```{r}
176 | cv_entries |>
177 | filter(type == 'prof-dev') |>
178 | mutate(what = str_c("", what, ", ", where)) |>
179 | select(year, what) |>
180 | kable(align = c("l", "l"), escape = FALSE)
181 | ```
182 |
183 | # Technical
184 | ## Skills
185 | ```{r}
186 | cv_entries |>
187 | filter(type == 'technical') |>
188 | mutate(icon = case_when(
189 | str_detect(what, "data analysis") ~ "chart-bar",
190 | str_detect(what, "research") ~ "flask",
191 | str_detect(what, "Web") ~ "laptop"
192 | )) |>
193 | rowwise() |>
194 | mutate(what = str_c(fa(icon), " ", what, " ", additional_info)) |>
195 | select(type, what) |>
196 | mutate(type = "") |>
197 | kable(escape=FALSE)
198 | ```
199 |
200 | ## R packages
201 | ```{r}
202 | cv_entries |>
203 | filter(type == 'software-rstats' & is.na(exclude)) |>
204 | replace_na(list(where = "", additional_info = "")) |>
205 | mutate(what = glue("{what}, {where}")) |>
206 | arrange(desc(year)) |>
207 | mutate(row_number = row_number()) |>
208 | select(year, what) |>
209 | kable(escape=FALSE)
210 | ```
211 |
212 | ## Apps
213 | ```{r}
214 | cv_entries |>
215 | filter(type == 'software-browser-extensions' & is.na(exclude)) |>
216 | replace_na(list(where = "", additional_info = "")) |>
217 | mutate(what = glue("{what}, {where}")) |>
218 | arrange(desc(year)) |>
219 | mutate(row_number = row_number()) |>
220 | select(year, what) |>
221 | kable(escape=FALSE)
222 | ```
223 |
224 |
225 | # Personal
226 | ## Skills & volunteering
227 | ```{r}
228 | cv_entries |>
229 | filter(type == 'volunteering') |>
230 | mutate(what = str_c("", what, "
", where)) |>
231 | mutate(what = ifelse(!is.na(additional_info), str_c(what, "
", additional_info), what)) |>
232 | arrange(desc(date_end)) |>
233 | select(year, what) |>
234 | kable(escape = FALSE)
235 | ```
236 |
--------------------------------------------------------------------------------
/cv-academic.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | output:
3 | pagedown::html_paged:
4 | number_sections: false
5 | css: ["css/academic_cv.css"]
6 | links-to-footnotes: true
7 | knit: pagedown::chrome_print
8 | ---
9 |
10 |
15 |
16 | ```{r setup, include=FALSE}
17 | knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE)
18 | library(tidyverse)
19 | library(knitr)
20 | library(glue)
21 | library(fontawesome)
22 | library(readxl)
23 | library(lubridate)
24 | source("R/utility-functions.R")
25 |
26 | cv_entries <- read_excel("cv_data.xlsx", sheet = "cv_entries") |>
27 | # provide range for entries with a year_end (e.g. '2013-2016')
28 | mutate(year_end = if_else(date_end == "present", "present", str_sub(date_end, start = 1, end = 4)),
29 | year = if_else(!is.na(year_end) & year(date) != year_end,
30 | str_c(year(date), " --- ", year_end),
31 | as.character(year(date))))
32 |
33 | publications <- read_excel("cv_data.xlsx", sheet = "publications") |>
34 | mutate(year = year(date),
35 | venue_abbrev = if_else(is.na(venue_abbrev), "", str_c(venue_abbrev, ": ")))
36 |
37 | ```
38 |
39 | ::: {.title-and-contact}
40 |
41 | :::::: {.title}
42 |