├── binder ├── runtime.txt └── install.R ├── netlify.toml ├── data ├── test.csv └── natsal_3_teaching_open.sav ├── static ├── favi.png ├── icon.png ├── profile.jpg ├── social.jpg ├── icon_check.svg ├── icon_slides.svg └── logo.svg ├── src ├── context.js ├── styles │ ├── chapter.module.sass │ ├── link.module.sass │ ├── index.module.sass │ ├── hint.module.sass │ ├── choice.module.sass │ ├── typography.module.sass │ ├── code.module.sass │ ├── layout.module.sass │ ├── button.module.sass │ ├── exercise.module.sass │ ├── slides.module.sass │ ├── index.sass │ └── reveal.css ├── components │ ├── typography.js │ ├── hint.js │ ├── button.js │ ├── link.js │ ├── choice.js │ ├── exercise.js │ ├── seo.js │ ├── slides.js │ ├── layout.js │ ├── code.js │ └── juniper.js ├── markdown.js ├── pages │ └── index.js └── templates │ └── chapter.js ├── exercises ├── exc_02_02.R ├── exc_01_04.R ├── solution_01_04.R ├── exc_01_05.R ├── exc_02_03.R ├── exc_02_04.R ├── test_01_04.R ├── exc_01_03.R ├── test_01_03.R ├── solution_01_03.R ├── exc_03_01.R └── solution_03_01.R ├── gatsby-browser.js ├── .prettierrc ├── slides └── chapter1_01_introduction.md ├── LICENSE ├── .gitignore ├── meta.json ├── theme.sass ├── chapters ├── chapter1.md ├── chapter2.md └── chapter3.md ├── package.json ├── gatsby-node.js ├── gatsby-config.js └── README.md /binder/runtime.txt: -------------------------------------------------------------------------------- 1 | r-2019-04-10 2 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | environment = { NODE_VERSION = "10" } 3 | -------------------------------------------------------------------------------- /data/test.csv: -------------------------------------------------------------------------------- 1 | Col1,Col2,Col3 2 | 1,2,3 3 | 4,5,6 4 | 7,8,9 5 | a,b,c 6 | -------------------------------------------------------------------------------- /static/favi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UKDataServiceOpen/r-course-starter/master/static/favi.png -------------------------------------------------------------------------------- /static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UKDataServiceOpen/r-course-starter/master/static/icon.png -------------------------------------------------------------------------------- /src/context.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const ChapterContext = React.createContext() 4 | -------------------------------------------------------------------------------- /static/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UKDataServiceOpen/r-course-starter/master/static/profile.jpg -------------------------------------------------------------------------------- /static/social.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UKDataServiceOpen/r-course-starter/master/static/social.jpg -------------------------------------------------------------------------------- /binder/install.R: -------------------------------------------------------------------------------- 1 | install.packages ("janitor") 2 | install.packages("tidyverse") 3 | install.packages ("haven") -------------------------------------------------------------------------------- /exercises/exc_02_02.R: -------------------------------------------------------------------------------- 1 | library (haven) 2 | ntsl3 <- haven::read_sav("data/natsal_3_teaching_open.sav") 3 | 4 | colnames(ntsl3) -------------------------------------------------------------------------------- /data/natsal_3_teaching_open.sav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UKDataServiceOpen/r-course-starter/master/data/natsal_3_teaching_open.sav -------------------------------------------------------------------------------- /exercises/exc_01_04.R: -------------------------------------------------------------------------------- 1 | # Create a variable storing the value of 2 + 2 2 | total <- ____ 3 | 4 | # Print the variable 5 | print(____) 6 | -------------------------------------------------------------------------------- /exercises/solution_01_04.R: -------------------------------------------------------------------------------- 1 | # Create a variable storing the value of 2 + 2 2 | total <- 2 + 2 3 | 4 | # Print the variable 5 | print(total) -------------------------------------------------------------------------------- /exercises/exc_01_05.R: -------------------------------------------------------------------------------- 1 | # test read in the dataset from /data/test.csv 2 | df <- read.csv2("data/test.csv", 3 | header= FALSE) 4 | 5 | df 6 | -------------------------------------------------------------------------------- /exercises/exc_02_03.R: -------------------------------------------------------------------------------- 1 | library (haven) 2 | ntsl3 <- haven::read_sav("data/natsal_3_teaching_open.sav") 3 | 4 | hist(ntsl3$dage1ch) 5 | summary(ntsl3$dage1ch, na.rm = T) -------------------------------------------------------------------------------- /src/styles/chapter.module.sass: -------------------------------------------------------------------------------- 1 | .pagination 2 | max-width: 100% 3 | width: var(--width-container) 4 | margin: 4rem auto 0 5 | display: flex 6 | justify-content: space-between 7 | -------------------------------------------------------------------------------- /static/icon_check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | import python from 'codemirror/mode/r/r' // eslint-disable-line no-unused-vars 2 | 3 | // This doesn't have to be here – but if we do import Juniper here, it's already 4 | // preloaded and cached when we dynamically import it in code.js. 5 | import Juniper from './src/components/juniper' // eslint-disable-line no-unused-vars 6 | -------------------------------------------------------------------------------- /src/styles/link.module.sass: -------------------------------------------------------------------------------- 1 | .root 2 | cursor: pointer 3 | border-bottom: 1px solid var(--color-theme) 4 | 5 | &:hover 6 | border-bottom-color: var(--color-front) 7 | 8 | .secondary 9 | border-bottom-color: var(--color-subtle-dark) 10 | 11 | &:hover 12 | border-bottom-color: var(--color-front) 13 | 14 | .hidden 15 | border-bottom: 0 16 | -------------------------------------------------------------------------------- /exercises/exc_02_04.R: -------------------------------------------------------------------------------- 1 | library (haven) 2 | library(ggplot2) 3 | library(dplyr) 4 | ntsl3 <- haven::read_sav("data/natsal_3_teaching_open.sav") 5 | 6 | #convert sex into factor 7 | ntsl3$sex <- as_factor(ntsl3$rsex) 8 | 9 | # Create summary data 10 | lr_gen <- ntsl3 %>% 11 | group_by(sex) %>% 12 | summarize(n = mean(dage1ch, na.rm = T)) 13 | 14 | # Plot it 15 | ggplot(lr_gen, aes(x = sex, y = n)) + 16 | geom_bar(aes(fill = sex), stat = "identity") + 17 | theme_minimal() -------------------------------------------------------------------------------- /exercises/test_01_04.R: -------------------------------------------------------------------------------- 1 | test <- function() { 2 | # Here we can either check objects created in the solution code, or the 3 | # string value of the solution, available as .solution. See the testTemplate 4 | # in the meta.json for details. 5 | if (total != 4) { 6 | stop("Don't doesn't quite look right") 7 | } 8 | if (!grepl("print(total)", .solution, fixed = TRUE)) { 9 | stop("Are you printing the correct variable?") 10 | } 11 | 12 | # This function is defined in the testTemplate 13 | success("Well done!") 14 | } 15 | -------------------------------------------------------------------------------- /exercises/exc_01_03.R: -------------------------------------------------------------------------------- 1 | library(ggplot2) 2 | 3 | mtcars$gear <- factor(mtcars$gear,levels=c(3,4,5), 4 | labels=c("3gears","4gears","5gears")) 5 | mtcars$am <- factor(mtcars$am,levels=c(0,1), 6 | labels=c("Automatic","Manual")) 7 | mtcars$cyl <- factor(mtcars$cyl,levels=c(4,6,8), 8 | labels=c("4cyl","6cyl","8cyl")) 9 | 10 | # Print the gear variable of mtcars 11 | print(____) 12 | 13 | # Assign the length of mtcars to some_var 14 | some_var <- ____ 15 | 16 | # Uncomment this to see the plot 17 | # print(qplot(mpg, data=mtcars, geom="density", fill=gear, alpha=I(.5))) 18 | -------------------------------------------------------------------------------- /exercises/test_01_03.R: -------------------------------------------------------------------------------- 1 | test <- function() { 2 | # Here we can either check objects created in the solution code, or the 3 | # string value of the solution, available as .solution. See the testTemplate 4 | # in the meta.json for details. 5 | if (some_var != length(mtcars)) { 6 | stop("Are you getting the correct length?") 7 | } 8 | if (!grepl("print(mtcars$gear)", .solution, fixed = TRUE)) { 9 | stop("Are you printing the correct variable?") 10 | } 11 | 12 | # This function is defined in the testTemplate 13 | success("Well done!") 14 | } 15 | -------------------------------------------------------------------------------- /exercises/solution_01_03.R: -------------------------------------------------------------------------------- 1 | library(ggplot2) 2 | 3 | mtcars$gear <- factor(mtcars$gear,levels=c(3,4,5), 4 | labels=c("3gears","4gears","5gears")) 5 | mtcars$am <- factor(mtcars$am,levels=c(0,1), 6 | labels=c("Automatic","Manual")) 7 | mtcars$cyl <- factor(mtcars$cyl,levels=c(4,6,8), 8 | labels=c("4cyl","6cyl","8cyl")) 9 | 10 | # Print the gear variable of mtcars 11 | print(mtcars$gear) 12 | 13 | # Assign the length of mtcars to some_var 14 | some_var <- length(mtcars) 15 | 16 | # Uncomment this to see the plot 17 | # print(qplot(mpg, data=mtcars, geom="density", fill=gear, alpha=I(.5))) 18 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "tabWidth": 4, 6 | "printWidth": 100, 7 | "overrides": [ 8 | { 9 | "files": "*.sass", 10 | "options": { 11 | "printWidth": 999 12 | } 13 | }, 14 | { 15 | "files": "*.md", 16 | "options": { 17 | "tabWidth": 2, 18 | "printWidth": 80, 19 | "proseWrap": "always", 20 | "htmlWhitespaceSensitivity": "strict" 21 | } 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /src/components/typography.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import classes from '../styles/typography.module.sass' 4 | 5 | export const H3 = ({ children }) =>

{children}

6 | export const Hr = () =>
7 | export const InlineCode = ({ children }) => {children} 8 | 9 | export const Ol = ({ children }) =>
    {children}
10 | export const Ul = ({ children }) => 11 | export const Li = ({ children }) =>
  • {children}
  • 12 | -------------------------------------------------------------------------------- /src/styles/index.module.sass: -------------------------------------------------------------------------------- 1 | .logo 2 | width: 300px 3 | height: auto 4 | max-width: 100% 5 | margin: 0 auto 6rem 6 | display: block 7 | color: var(--color-theme) 8 | 9 | .chapter 10 | width: var(--width-container) 11 | max-width: 100% 12 | margin: 0 auto 13 | padding: 2rem 14 | border: 1px solid var(--color-subtle-medium) 15 | border-radius: var(--border-radius) 16 | margin-bottom: 2rem 17 | 18 | .chapter-title 19 | font-family: var(--font-display) 20 | font-size: var(--font-size-lg) 21 | margin-bottom: 1.5rem 22 | 23 | .chapter-desc 24 | font-size: var(--font-size-sm) 25 | color: var(--color-subtle-dark) 26 | margin-bottom: 0 27 | -------------------------------------------------------------------------------- /static/icon_slides.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/styles/hint.module.sass: -------------------------------------------------------------------------------- 1 | .root 2 | width: 100% 3 | 4 | .actions 5 | padding-left: 2rem 6 | 7 | .label 8 | cursor: pointer 9 | background: var(--color-subtle-light) 10 | border-width: 0 1px 1px 1px 11 | border-style: solid 12 | border-color: var(--color-subtle-medium) 13 | padding: 0.5rem 1rem 0.5rem 14 | border-bottom-left-radius: var(--border-radius) 15 | border-bottom-right-radius: var(--border-radius) 16 | font-family: var(--font-primary) 17 | font-size: var(--font-size-sm) 18 | font-weight: bold 19 | margin-right: 1rem 20 | 21 | .content 22 | background: var(--color-subtle-light) 23 | padding: 1rem 2rem 24 | font-size: var(--font-size-sm) 25 | border-bottom: 1px solid var(--color-subtle-medium) 26 | 27 | p, ol, ul 28 | margin-bottom: 0 29 | -------------------------------------------------------------------------------- /src/markdown.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import rehypeReact from 'rehype-react' 3 | 4 | import Exercise from './components/exercise' 5 | import CodeBlock from './components/code' 6 | import { Link } from './components/link' 7 | import Slides from './components/slides' 8 | import Choice, { Option } from './components/choice' 9 | import { H3, Hr, Ol, Ul, Li, InlineCode } from './components/typography' 10 | 11 | export const renderAst = new rehypeReact({ 12 | createElement: React.createElement, 13 | components: { 14 | exercise: Exercise, 15 | slides: Slides, 16 | codeblock: CodeBlock, 17 | choice: Choice, 18 | opt: Option, 19 | a: Link, 20 | hr: Hr, 21 | h3: H3, 22 | ol: Ol, 23 | ul: Ul, 24 | li: Li, 25 | code: InlineCode, 26 | }, 27 | }).Compiler 28 | -------------------------------------------------------------------------------- /exercises/exc_03_01.R: -------------------------------------------------------------------------------- 1 | # Imports libaries and supress warnings 2 | suppressMessages(library(dplyr, warn.conflict = FALSE, quietly = TRUE)) 3 | library(haven) 4 | library(dplyr) 5 | 6 | 7 | # Read in our dataset 8 | ntsl3 <- haven::read_sav("data/natsal_3_teaching_open.sav") 9 | 10 | # Convert columns 11 | ntsl3$religimp <- as_factor(ntsl3$religimp) 12 | ntsl3$snnolov <- as_factor(ntsl3$snnolov) 13 | ntsl3$snnolov <-recode_factor(ntsl3$snnolov, "Don't know" = NA_character_) 14 | ntsl3$snpres <- as_factor(ntsl3$snpres) 15 | ntsl3$snpres <-recode_factor(ntsl3$snpres, "Don't know" = NA_character_) 16 | ntsl3$snmedia<- as_factor(ntsl3$snmedia) 17 | ntsl3$snmedia <-recode_factor(ntsl3$snmedia, "Don't know" = NA_character_) 18 | ntsl3$snearly<- as_factor(ntsl3$snearly) 19 | ntsl3$snearly <-recode_factor(ntsl3$snearly, "Don't know" = NA_character_) 20 | ntsl3$rsex<- as_factor(ntsl3$rsex) 21 | ntsl3$agrp<- as_factor(ntsl3$agrp) 22 | 23 | # Print out all column names 24 | names(ntsl3) -------------------------------------------------------------------------------- /exercises/solution_03_01.R: -------------------------------------------------------------------------------- 1 | # Imports libaries and supress warnings 2 | suppressMessages(library(dplyr, warn.conflict = FALSE, quietly = TRUE)) 3 | library(haven) 4 | library(dplyr) 5 | 6 | 7 | # Read in our dataset 8 | ntsl3 <- haven::read_sav("data/natsal_3_teaching_open.sav") 9 | 10 | # Convert columns 11 | ntsl3$religimp <- as_factor(ntsl3$religimp) 12 | ntsl3$snnolov <- as_factor(ntsl3$snnolov) 13 | ntsl3$snnolov <-recode_factor(ntsl3$snnolov, "Don't know" = NA_character_) 14 | ntsl3$snpres <- as_factor(ntsl3$snpres) 15 | ntsl3$snpres <-recode_factor(ntsl3$snpres, "Don't know" = NA_character_) 16 | ntsl3$snmedia<- as_factor(ntsl3$snmedia) 17 | ntsl3$snmedia <-recode_factor(ntsl3$snmedia, "Don't know" = NA_character_) 18 | ntsl3$snearly<- as_factor(ntsl3$snearly) 19 | ntsl3$snearly <-recode_factor(ntsl3$snearly, "Don't know" = NA_character_) 20 | ntsl3$rsex<- as_factor(ntsl3$rsex) 21 | ntsl3$agrp<- as_factor(ntsl3$agrp) 22 | 23 | # Print out the number of variables 24 | print('The number of variables is:') 25 | length(names(ntsl3)) -------------------------------------------------------------------------------- /src/styles/choice.module.sass: -------------------------------------------------------------------------------- 1 | .option 2 | margin-bottom: 0.5rem 3 | margin-left: 1rem 4 | 5 | .input 6 | display: none 7 | 8 | .label 9 | cursor: pointer 10 | display: flex 11 | align-items: flex-start 12 | 13 | &:before 14 | content: "" 15 | flex: 0 0 20px 16 | width: 20px 17 | height: 20px 18 | display: inline-block 19 | border: 2px solid var(--color-theme) 20 | border-radius: 50% 21 | margin-right: 1rem 22 | margin-top: 0.5rem 23 | 24 | input[type="radio"]:checked + &:before 25 | border-width: 7px 26 | 27 | .answer 28 | padding: 2rem 29 | border-radius: var(--border-radius) 30 | background: var(--color-incorrect-light) 31 | margin-bottom: 2rem 32 | 33 | p 34 | margin-bottom: 0 35 | display: inline 36 | 37 | .correct 38 | background: var(--color-correct-light) 39 | 40 | .answer-label 41 | color: var(--color-incorrect-dark) 42 | 43 | .answer-label-correct 44 | color: var(--color-correct-dark) 45 | -------------------------------------------------------------------------------- /src/styles/typography.module.sass: -------------------------------------------------------------------------------- 1 | .h3 2 | font-size: var(--font-size-md) 3 | font-weight: bold 4 | margin-bottom: 2rem 5 | 6 | .hr 7 | border: 0 8 | height: 0 9 | margin-top: 7rem 10 | margin-bottom: 7rem 11 | 12 | .ul, 13 | .ol 14 | margin-bottom: 3rem 15 | 16 | .ol 17 | list-style: none 18 | counter-reset: ul 19 | 20 | .li 21 | margin-left: 0.5rem 22 | 23 | &:before 24 | margin-right: 1rem 25 | font-weight: bold 26 | counter-increment: ul 27 | content: counter(ul) ". " 28 | 29 | .li 30 | margin-left: 2rem 31 | 32 | .code 33 | background: var(--color-background-code) 34 | padding: 2px 5px 35 | border-radius: var(--border-radius) 36 | 37 | pre & 38 | background: var(--color-subtle-light) 39 | 40 | \:global 41 | pre code 42 | background: var(--color-subtle-light) 43 | 44 | p code, li code 45 | background: var(--color-background-code) 46 | padding: 2px 5px 47 | border-radius: var(--border-radius) 48 | -------------------------------------------------------------------------------- /slides/chapter1_01_introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: slides 3 | --- 4 | 5 | # Introduction 6 | 7 | Notes: Text at the end of a slide prefixed like this will be displayed as 8 | speaker notes on the side. Slides can be separated with a divider: ---. 9 | 10 | --- 11 | 12 | # This is a slide 13 | 14 | ```r 15 | # Print something 16 | print("Hello world", quote = FALSE) 17 | ``` 18 | 19 | ```out 20 | Hello world 21 | ``` 22 | 23 | - Slides can have code, bullet points, tables and pretty much all other Markdown 24 | elements. 25 | - This is another bullet point. 26 | 27 | This image is in /static 28 | 29 | Notes: Some more notes go here 30 | 31 | --- 32 | 33 | # Let's practice! 34 | 35 | Notes: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam tristique 36 | libero at est congue, sed vestibulum tortor laoreet. Aenean egestas massa non 37 | commodo consequat. Curabitur faucibus, sapien vitae euismod imperdiet, arcu erat 38 | semper urna, in accumsan sapien dui ac mi. Pellentesque felis lorem, semper nec 39 | velit nec, consectetur placerat enim. 40 | -------------------------------------------------------------------------------- /src/components/hint.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback } from 'react' 2 | 3 | import classes from '../styles/hint.module.sass' 4 | 5 | export const Hint = ({ expanded = false, actions = [], children }) => { 6 | const [isExpanded, setIsExpanded] = useState(expanded) 7 | const handleExpand = useCallback(() => setIsExpanded(!isExpanded), [isExpanded]) 8 | return ( 9 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (C) 2019 Ines Montani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (http://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # Typescript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # dotenv environment variables file 57 | .env 58 | 59 | # gatsby files 60 | .cache/ 61 | public 62 | 63 | # Mac files 64 | .DS_Store 65 | 66 | # Yarn 67 | yarn-error.log 68 | .pnp/ 69 | .pnp.js 70 | # Yarn Integrity file 71 | .yarn-integrity 72 | 73 | yarn.lock -------------------------------------------------------------------------------- /src/styles/code.module.sass: -------------------------------------------------------------------------------- 1 | .root 2 | margin-left: -2rem 3 | width: calc(100% + 4rem) 4 | margin-bottom: 5rem 5 | 6 | .cell 7 | background: var(--syntax-background) 8 | border-top: 1px solid var(--color-subtle-medium) 9 | 10 | & > div:first-child 11 | padding: 1.5rem 2rem 1rem 12 | position: relative 13 | 14 | &:before 15 | content: "Input" 16 | display: inline-block 17 | border-radius: var(--border-radius) 18 | color: var(--syntax-background) 19 | background: var(--syntax-text) 20 | padding: 0 0.5rem 21 | position: absolute 22 | top: 1rem 23 | right: 1.5rem 24 | font-size: var(--font-size-xs) 25 | font-weight: bold 26 | text-transform: uppercase 27 | z-index: 5 28 | 29 | .output 30 | padding: 2rem 9rem 2rem 3rem 31 | background: var(--syntax-text) 32 | color: var(--syntax-background) 33 | position: relative 34 | 35 | &:before 36 | content: "Output" 37 | display: inline-block 38 | border-radius: var(--border-radius) 39 | background: var(--syntax-background) 40 | color: var(--syntax-text) 41 | padding: 0 0.5rem 42 | position: absolute 43 | top: 1rem 44 | right: 1.5rem 45 | font-size: var(--font-size-xs) 46 | font-weight: bold 47 | text-transform: uppercase 48 | -------------------------------------------------------------------------------- /src/components/button.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classNames from 'classnames' 3 | 4 | import IconCheck from '../../static/icon_check.svg' 5 | import classes from '../styles/button.module.sass' 6 | 7 | export const Button = ({ Component = 'button', children, onClick, variant, small, className }) => { 8 | const buttonClassNames = classNames(classes.root, className, { 9 | [classes.primary]: variant === 'primary', 10 | [classes.secondary]: variant === 'secondary', 11 | [classes.small]: !!small, 12 | }) 13 | return ( 14 | 15 | {children} 16 | 17 | ) 18 | } 19 | 20 | export const CompleteButton = ({ completed, toggleComplete, small = true }) => { 21 | const buttonClassNames = classNames({ 22 | [classes.completeInactive]: !completed, 23 | [classes.completeActive]: completed, 24 | }) 25 | return ( 26 | 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "courseId": "course-starter-r", 3 | "title": "My cool online course", 4 | "slogan": "A free online course", 5 | "description": "This course covers exploring the open teaching NATSAL dataset. This course was built in collaboration with the QStep programme at the University of Manchester.", 6 | "bio": "The UK Data Service are pioneers in data curation and managing long-term research access to high quality data, our expertise continues to transform social science teaching and learning.", 7 | "siteUrl": "https://course-starter-r.netlify.com", 8 | "twitter": "spacy_io", 9 | "fonts": "IBM+Plex+Mono:500|IBM+Plex+Sans:700|Lato:400,400i,700,700i", 10 | "testTemplate": "success <- function(text) {\n cat(paste(\"\\033[32m\", text, \"\\033[0m\", sep = \"\"))\n}\n\n.solution <- \"${solutionEscaped}\"\n\n${solution}\n\n${test}\ntryCatch({\n test()\n}, error = function(e) {\n cat(paste(\"\\033[31m\", e[1], \"\\033[0m\", sep = \"\"))\n})", 11 | "juniper": { 12 | "repo": "UKDataServiceOpen/r-course-starter", 13 | "branch": "master", 14 | "lang": "r", 15 | "kernelType": "ir", 16 | "debug": false 17 | }, 18 | "showProfileImage": true, 19 | "footerLinks": [ 20 | { "text": "Website", "url": "https://ukdataservice.ac.uk/" }, 21 | { "text": "Source", "url": "https://github.com/ines/course-starter-r" }, 22 | { "text": "Built with ♥", "url": "https://github.com/ines/course-starter-r" } 23 | ], 24 | "theme": "#de7878" 25 | } 26 | -------------------------------------------------------------------------------- /src/components/link.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Link as GatsbyLink } from 'gatsby' 4 | import classNames from 'classnames' 5 | 6 | import classes from '../styles/link.module.sass' 7 | 8 | export const Link = ({ children, to, href, onClick, variant, hidden, className, ...other }) => { 9 | const dest = to || href 10 | const external = /(http(s?)):\/\//gi.test(dest) 11 | const linkClassNames = classNames(classes.root, className, { 12 | [classes.hidden]: hidden, 13 | [classes.secondary]: variant === 'secondary', 14 | }) 15 | 16 | if (!external) { 17 | if ((dest && /^#/.test(dest)) || onClick) { 18 | return ( 19 | 20 | {children} 21 | 22 | ) 23 | } 24 | return ( 25 | 26 | {children} 27 | 28 | ) 29 | } 30 | return ( 31 | 38 | {children} 39 | 40 | ) 41 | } 42 | 43 | Link.propTypes = { 44 | children: PropTypes.node.isRequired, 45 | to: PropTypes.string, 46 | href: PropTypes.string, 47 | onClick: PropTypes.func, 48 | variant: PropTypes.oneOf(['secondary', null]), 49 | hidden: PropTypes.bool, 50 | className: PropTypes.string, 51 | } 52 | -------------------------------------------------------------------------------- /src/styles/layout.module.sass: -------------------------------------------------------------------------------- 1 | .root 2 | width: 100% 3 | 4 | .content 5 | margin-bottom: 4rem 6 | padding: 6rem 1rem 1rem 7 | 8 | .logo 9 | color: var(--color-theme) 10 | position: absolute 11 | top: 2rem 12 | left: 2rem 13 | 14 | @media(min-width: 1200px) 15 | .logo 16 | position: fixed 17 | 18 | .header 19 | width: var(--width-container) 20 | max-width: 100% 21 | margin: 5rem auto 22 | 23 | .title 24 | font-size: var(--font-size-xl) 25 | font-family: var(--font-display) 26 | line-height: 1.3 27 | margin-bottom: 2rem 28 | 29 | .footer 30 | background: var(--color-subtle-light) 31 | width: 100% 32 | padding: 3rem 33 | margin-top: 5.5rem 34 | 35 | .footer-content 36 | max-width: 100% 37 | width: var(--width-container) 38 | margin: 0 auto 39 | font-size: var(--font-size-sm) 40 | justify-content: space-between 41 | flex-flow: row wrap 42 | 43 | @media(min-width: 900px) 44 | .footer-content 45 | display: flex 46 | 47 | .footer-section 48 | flex: 0 0 48% 49 | padding: 0 2rem 50 | margin-bottom: 1rem 51 | 52 | .footer-links 53 | flex: 0 0 100% 54 | padding-top: 2rem 55 | font-size: var(--font-size-xs) 56 | text-align: center 57 | color: var(--color-subtle-dark) 58 | 59 | .footer-link 60 | display: inline 61 | 62 | &:not(:last-child) 63 | margin-right: 1rem 64 | 65 | &:after 66 | content: "\b7" 67 | margin-left: 1rem 68 | 69 | .profile 70 | width: var(--size-profile-pic) 71 | height: var(--size-profile-pic) 72 | border-radius: 50% 73 | float: right 74 | margin: 0 0 1rem 1rem 75 | shape-outside: circle() 76 | -------------------------------------------------------------------------------- /src/styles/button.module.sass: -------------------------------------------------------------------------------- 1 | .root 2 | border: 0 3 | background: var(--color-button-secondary) 4 | color: var(--color-button-secondary-contrast) 5 | padding: 0.75rem 1rem 6 | font-family: var(--font-primary) 7 | font-size: var(--font-size-sm) 8 | font-weight: bold 9 | border-radius: var(--border-radius) 10 | margin-top: 3rem 11 | margin-bottom: 2.25rem 12 | margin-left: 2.5rem 13 | transition: opacity 0.2s ease 14 | box-shadow: 0 0 3px var(--color-subtle-medium) 15 | 16 | &:hover 17 | opacity: 0.9 18 | box-shadow: none 19 | 20 | .primary 21 | background: var(--color-button-primary) 22 | color: var(--color-button-primary-contrast) 23 | 24 | .secondary 25 | background: var(--color-theme) 26 | color: var(--color-theme-contrast) 27 | 28 | .small 29 | padding: 0.75rem 1rem 30 | margin: 0 31 | font-size: var(--font-size-sm) 32 | 33 | &:not(:last-child) 34 | margin-right: 1rem 35 | 36 | .complete-icon 37 | position: relative 38 | top: 0.15rem 39 | margin-right: 0.5rem 40 | 41 | .complete-inactive 42 | &:hover 43 | opacity: 1 44 | background: var(--color-button-primary) 45 | color: var(--color-button-primary-contrast) 46 | 47 | .complete-active 48 | background: var(--color-button-primary) 49 | color: var(--color-button-primary-contrast) 50 | 51 | &:hover 52 | opacity: 1 53 | background: var(--color-button-danger) 54 | color: var(--color-button-danger-contrast) 55 | 56 | .complete-label 57 | .root:hover & 58 | display: none 59 | 60 | .complete-label-hover 61 | display: none 62 | 63 | .root:hover & 64 | display: inline 65 | -------------------------------------------------------------------------------- /src/styles/exercise.module.sass: -------------------------------------------------------------------------------- 1 | .root 2 | width: var(--width-container) 3 | max-width: 100% 4 | padding: 1rem 2rem 5 | border: 1px solid var(--color-subtle-medium) 6 | border-radius: var(--border-radius) 7 | margin: 0 auto 2rem 8 | position: relative 9 | background: var(--color-back) 10 | 11 | .wide 12 | width: 95% 13 | 14 | .expanded 15 | box-shadow: 0 0 10px var(--color-subtle-medium) 16 | 17 | .completed 18 | color: var(--color-subtle-dark) 19 | background: var(--color-subtle-light) 20 | 21 | .title 22 | cursor: pointer 23 | user-select: none 24 | font-size: var(--font-size-lg) 25 | font-family: var(--font-display) 26 | 27 | .id 28 | background: var(--color-theme) 29 | color: var(--color-theme-contrast) 30 | font-size: 0.8em 31 | width: 1.75em 32 | height: 1.75em 33 | display: inline-flex 34 | border-radius: 50% 35 | justify-content: center 36 | align-items: center 37 | margin-right: 1rem 38 | 39 | .id-completed 40 | background: var(--color-button-primary) 41 | color: var(--color-button-primary-contrast) 42 | 43 | .title-expanded 44 | margin-bottom: 3rem 45 | 46 | .icon 47 | float: right 48 | vertical-align: middle 49 | margin-right: 0.5rem 50 | 51 | .content 52 | visibility: hidden 53 | height: 0 54 | opacity: 0 55 | z-index: 0 56 | overflow: hidden 57 | 58 | .footer 59 | width: 100% 60 | text-align: right 61 | margin-top: 1rem 62 | margin-bottom: 1rem 63 | 64 | .button 65 | cursor: pointer 66 | border: 0 67 | background: var(--color-theme) 68 | color: var(--color-theme-contrast) 69 | padding: 0.75rem 1rem 70 | font-family: var(--font-primary) 71 | font-size: var(--font-size-sm) 72 | font-weight: bold 73 | border-radius: var(--border-radius) 74 | -------------------------------------------------------------------------------- /theme.sass: -------------------------------------------------------------------------------- 1 | \:root 2 | --font-primary: Lato, Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol' 3 | --font-display: 'IBM Plex Sans', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol' 4 | --font-code: 'IBM Plex Mono', Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace 5 | 6 | // Font Sizes 7 | --font-size-xs: 1.15rem 8 | --font-size-sm: 1.3rem 9 | --font-size-md: 1.5rem 10 | --font-size-lg: 1.8rem 11 | --font-size-xl: 2.4rem 12 | 13 | // Line heights 14 | --line-height: 1.6 15 | 16 | // Colors 17 | --color-back: #fff 18 | --color-front: #444 19 | --color-theme: #702082 20 | --color-theme-contrast: #fff 21 | --color-subtle-dark: #777 22 | --color-subtle-medium: #eee 23 | --color-subtle-light: #f9f9f9 24 | --color-background-code: #7a818117 25 | 26 | --color-button-primary: #29c15b 27 | --color-button-primary-contrast: #fff 28 | --color-button-secondary: #cdcdd4 29 | --color-button-secondary-contrast: #686873 30 | --color-button-danger: #d21313 31 | --color-button-danger-contrast: #fff 32 | 33 | --color-correct-light: #e3fde8 34 | --color-correct-dark: #007737 35 | --color-incorrect-light: #fde7e7 36 | --color-incorrect-dark: #d21313 37 | 38 | // Syntax Highlighting 39 | // based on https://github.com/sdras/night-owl-vscode-theme 40 | --syntax-background: #f7f7f7 41 | --syntax-text: #403f53 42 | --syntax-selected-background: #7a81812b 43 | --syntax-comment: #989fb1 44 | --syntax-tag: #994cc3 45 | --syntax-number: #aa0982 46 | --syntax-selector: #994cc3 47 | --syntax-operator: #994cc3 48 | --syntax-function: #4876d6 49 | --syntax-keyword: #994cc3 50 | --syntax-regex: #5ca7e4 51 | 52 | --ansi-green: #12dc55 53 | --ansi-red: #f76464 54 | --ansi-cyan: #00d8ff 55 | 56 | // Sizes 57 | --width-container: 800px 58 | --size-profile-pic: 125px 59 | --border-radius: 5px 60 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { graphql } from 'gatsby' 3 | 4 | import Layout from '../components/layout' 5 | import { Link } from '../components/link' 6 | import Logo from '../../static/logo.svg' 7 | 8 | import classes from '../styles/index.module.sass' 9 | 10 | export default ({ data }) => { 11 | const siteMetadata = data.site.siteMetadata 12 | const chapters = data.allMarkdownRemark.edges.map(({ node }) => ({ 13 | slug: node.fields.slug, 14 | title: node.frontmatter.title, 15 | description: node.frontmatter.description, 16 | })) 17 | return ( 18 | 19 | 20 | {chapters.map(({ slug, title, description }) => ( 21 |
    22 |

    23 | 24 | {title} 25 | 26 |

    27 |

    28 | 29 | {description} 30 | 31 |

    32 |
    33 | ))} 34 |
    35 | ) 36 | } 37 | 38 | export const pageQuery = graphql` 39 | { 40 | site { 41 | siteMetadata { 42 | title 43 | } 44 | } 45 | allMarkdownRemark( 46 | sort: { fields: [frontmatter___title], order: ASC } 47 | filter: { frontmatter: { type: { eq: "chapter" } } } 48 | ) { 49 | edges { 50 | node { 51 | fields { 52 | slug 53 | } 54 | frontmatter { 55 | title 56 | description 57 | } 58 | } 59 | } 60 | } 61 | } 62 | ` 63 | -------------------------------------------------------------------------------- /chapters/chapter1.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Chapter 1: Getting started' 3 | description: 4 | 'This chapter will teach you about many cool things and introduce you to the 5 | most important concepts of the course.' 6 | prev: null 7 | next: /chapter2 8 | type: chapter 9 | id: 1 10 | --- 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Let's ask some questions about the slides. Whats the correct answer? 22 | 23 | 24 | 25 | 26 | This is not the correct answer. 27 | 28 | 29 | 30 | 31 | 32 | Good job! 33 | 34 | 35 | 36 | 37 | 38 | This is not correct either. 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | This is a code exercise. The content can be formatted in simple Markdown – so 48 | you can have **bold text**, `code` or [links](https://spacy.io) or lists, like 49 | the one for the instructions below. 50 | 51 | - These are instructions and they can have bullet points. 52 | - The code block below will look for the files `exc_01_03`, `solution_01_03` and 53 | `test_01_03` in `/exercises`. 54 | 55 | 56 | 57 | This is a hint. 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | This is a hint. 68 | 69 | 70 | 71 | 72 | 73 | 74 | No, think about what 2 + 2 might evaluate to? 75 | 76 | 77 | 78 | 79 | 80 | Good job! 2 + 2 is 4! 81 | 82 | 83 | 84 | 85 | 86 | A little too high, guess again! 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | you can import data using the read_csv function. 96 | 97 | 98 | 99 | This is a hint. 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "course-starter-r", 3 | "private": true, 4 | "description": "Starter package to build interactive R courses", 5 | "version": "0.0.1", 6 | "author": "Ines Montani ", 7 | "dependencies": { 8 | "@illinois/react-use-local-storage": "^1.1.0", 9 | "@jupyterlab/outputarea": "^0.19.1", 10 | "@jupyterlab/rendermime": "^0.19.1", 11 | "@phosphor/widgets": "^1.6.0", 12 | "autoprefixer": "^9.4.7", 13 | "classnames": "^2.2.6", 14 | "codemirror": "^5.43.0", 15 | "gatsby": "^2.1.4", 16 | "gatsby-image": "^2.0.29", 17 | "gatsby-plugin-manifest": "^2.0.17", 18 | "gatsby-plugin-offline": "^2.0.23", 19 | "gatsby-plugin-react-helmet": "^3.0.6", 20 | "gatsby-plugin-react-svg": "^2.1.1", 21 | "gatsby-plugin-sass": "^2.0.10", 22 | "gatsby-plugin-sharp": "^2.0.29", 23 | "gatsby-plugin-sitemap": "^2.0.5", 24 | "gatsby-remark-copy-linked-files": "^2.0.9", 25 | "gatsby-remark-images": "^3.0.4", 26 | "gatsby-remark-prismjs": "^3.2.4", 27 | "gatsby-remark-smartypants": "^2.0.8", 28 | "gatsby-remark-unwrap-images": "^1.0.1", 29 | "gatsby-source-filesystem": "^2.0.20", 30 | "gatsby-transformer-remark": "^2.2.5", 31 | "gatsby-transformer-sharp": "^2.1.17", 32 | "juniper-js": "^0.1.0", 33 | "node-sass": "^4.11.0", 34 | "prismjs": "^1.15.0", 35 | "react": "^16.8.2", 36 | "react-dom": "^16.8.2", 37 | "react-helmet": "^5.2.0", 38 | "rehype-react": "^3.1.0", 39 | "remark-react": "^5.0.1", 40 | "reveal.js": "^3.8.0" 41 | }, 42 | "scripts": { 43 | "build": "gatsby build", 44 | "dev": "gatsby develop", 45 | "lint": "eslint **", 46 | "clear": "rm -rf .cache", 47 | "test": "echo \"Write tests! -> https://gatsby.app/unit-testing\"" 48 | }, 49 | "devDependencies": { 50 | "browser-monads": "^1.0.0", 51 | "prettier": "^1.16.4" 52 | }, 53 | "repository": { 54 | "type": "git", 55 | "url": "https://github.com/ines/course-starter-python" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/templates/chapter.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { graphql, navigate } from 'gatsby' 3 | import useLocalStorage from '@illinois/react-use-local-storage' 4 | 5 | import { renderAst } from '../markdown' 6 | import { ChapterContext } from '../context' 7 | import Layout from '../components/layout' 8 | import { Button } from '../components/button' 9 | 10 | import classes from '../styles/chapter.module.sass' 11 | 12 | const Template = ({ data }) => { 13 | const { markdownRemark, site } = data 14 | const { courseId } = site.siteMetadata 15 | const { frontmatter, htmlAst } = markdownRemark 16 | const { title, description, prev, next, id } = frontmatter 17 | const [activeExc, setActiveExc] = useState(null) 18 | const [completed, setCompleted] = useLocalStorage(`${courseId}-completed-${id}`, []) 19 | const html = renderAst(htmlAst) 20 | const buttons = [ 21 | { slug: prev, text: '« Previous Chapter' }, 22 | { slug: next, text: 'Next Chapter »' }, 23 | ] 24 | 25 | return ( 26 | 27 | 28 | {html} 29 | 30 |
    31 | {buttons.map(({ slug, text }) => ( 32 |
    33 | {slug && ( 34 | 37 | )} 38 |
    39 | ))} 40 |
    41 |
    42 |
    43 | ) 44 | } 45 | 46 | export default Template 47 | 48 | export const pageQuery = graphql` 49 | query($slug: String!) { 50 | site { 51 | siteMetadata { 52 | courseId 53 | } 54 | } 55 | markdownRemark(fields: { slug: { eq: $slug } }) { 56 | htmlAst 57 | frontmatter { 58 | id 59 | title 60 | description 61 | next 62 | prev 63 | } 64 | } 65 | } 66 | ` 67 | -------------------------------------------------------------------------------- /src/components/choice.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback } from 'react' 2 | import classNames from 'classnames' 3 | 4 | import { Button } from './button' 5 | import classes from '../styles/choice.module.sass' 6 | 7 | const Choice = ({ id = '0', children = [] }) => { 8 | const [selected, setSelected] = useState(null) 9 | const [answer, setAnswer] = useState(null) 10 | const handleAnswer = useCallback(() => setAnswer(selected), [selected]) 11 | const options = children.filter(child => child !== '\n') 12 | return ( 13 | <> 14 | {options.map(({ key, props }, i) => ( 15 |

    16 | setSelected(i)} 24 | /> 25 |

    31 | ))} 32 | 35 | {options.map(({ key, props }, i) => { 36 | const isCorrect = !!props.correct 37 | return answer === i ? ( 38 |
    42 | 47 | {isCorrect ? "That's correct! " : 'Incorrect. '} 48 | 49 | {props.children} 50 |
    51 | ) : null 52 | })} 53 | 54 | ) 55 | } 56 | 57 | export const Option = ({ children }) => { 58 | return children 59 | } 60 | 61 | export default Choice 62 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { createFilePath } = require('gatsby-source-filesystem') 3 | 4 | const chapterTemplate = path.resolve('src/templates/chapter.js') 5 | 6 | function replacePath(pagePath) { 7 | return pagePath === `/` ? pagePath : pagePath.replace(/\/$/, ``) 8 | } 9 | 10 | async function onCreateNode({ 11 | node, 12 | actions, 13 | getNode, 14 | loadNodeContent, 15 | createNodeId, 16 | createContentDigest, 17 | }) { 18 | const { createNodeField, createNode, createParentChildLink } = actions 19 | if (node.internal.type === 'MarkdownRemark') { 20 | const slug = createFilePath({ node, getNode, basePath: 'chapters', trailingSlash: false }) 21 | createNodeField({ name: 'slug', node, value: slug }) 22 | } else if (node.extension === 'r') { 23 | // Load the contents of the Python file and make it available via GraphQL 24 | // https://www.gatsbyjs.org/docs/creating-a-transformer-plugin/ 25 | const content = await loadNodeContent(node) 26 | const contentDigest = createContentDigest(content) 27 | const id = createNodeId(`${node.id}-code`) 28 | const internal = { type: 'Code', contentDigest } 29 | const codeNode = { 30 | id, 31 | parent: node.id, 32 | children: [], 33 | code: content, 34 | name: node.name, 35 | internal, 36 | } 37 | createNode(codeNode) 38 | createParentChildLink({ parent: node, child: codeNode }) 39 | } 40 | } 41 | 42 | exports.onCreateNode = onCreateNode 43 | 44 | exports.createPages = ({ actions, graphql }) => { 45 | const { createPage } = actions 46 | return graphql(` 47 | { 48 | allMarkdownRemark { 49 | edges { 50 | node { 51 | frontmatter { 52 | title 53 | type 54 | } 55 | fields { 56 | slug 57 | } 58 | } 59 | } 60 | } 61 | } 62 | `).then(result => { 63 | if (result.errors) { 64 | return Promise.reject(result.errors) 65 | } 66 | const posts = result.data.allMarkdownRemark.edges.filter( 67 | ({ node }) => node.frontmatter.type == 'chapter' 68 | ) 69 | posts.forEach(({ node }) => { 70 | createPage({ 71 | path: replacePath(node.fields.slug), 72 | component: chapterTemplate, 73 | context: { slug: node.fields.slug }, 74 | }) 75 | }) 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /src/components/exercise.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useCallback, useContext, useEffect } from 'react' 2 | import classNames from 'classnames' 3 | 4 | import { Button, CompleteButton } from './button' 5 | import { ChapterContext } from '../context' 6 | import IconSlides from '../../static/icon_slides.svg' 7 | import classes from '../styles/exercise.module.sass' 8 | 9 | const Exercise = ({ id, title, type, children }) => { 10 | const excRef = useRef() 11 | const excId = parseInt(id) 12 | const { activeExc, setActiveExc, completed, setCompleted } = useContext(ChapterContext) 13 | const isExpanded = activeExc === excId 14 | const isCompleted = completed.includes(excId) 15 | useEffect(() => { 16 | if (isExpanded && excRef.current) { 17 | excRef.current.scrollIntoView() 18 | } 19 | }, [isExpanded]) 20 | const handleExpand = useCallback(() => setActiveExc(isExpanded ? null : excId), [ 21 | isExpanded, 22 | excId, 23 | ]) 24 | const handleNext = useCallback(() => setActiveExc(excId + 1)) 25 | const handleSetCompleted = useCallback(() => { 26 | const newCompleted = isCompleted 27 | ? completed.filter(v => v !== excId) 28 | : [...completed, excId] 29 | setCompleted(newCompleted) 30 | }, [isCompleted, completed, excId]) 31 | const rootClassNames = classNames(classes.root, { 32 | [classes.expanded]: isExpanded, 33 | [classes.wide]: isExpanded && type === 'slides', 34 | [classes.completed]: !isExpanded && isCompleted, 35 | }) 36 | const titleClassNames = classNames(classes.title, { 37 | [classes.titleExpanded]: isExpanded, 38 | }) 39 | return ( 40 |
    41 |

    42 | 43 | 46 | {excId} 47 | 48 | {title} 49 | 50 | {type === 'slides' && } 51 |

    52 | {isExpanded && ( 53 |
    54 | {children} 55 |
    56 | 60 | 63 |
    64 |
    65 | )} 66 |
    67 | ) 68 | } 69 | 70 | export default Exercise 71 | -------------------------------------------------------------------------------- /chapters/chapter2.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Chapter 2: What age do people have children?' 3 | description: 4 | 'Explore the National Survey of Sexual Attitudes and Lifestyles to find out what age people tend to have children' 5 | prev: /chapter1 6 | next: null 7 | type: chapter 8 | id: 2 9 | --- 10 | 11 | 12 | The age of having children has been increasing since the middle of the 1970s (ONS). 13 | 14 | What is the average age of having children? How does the age of first child vary in the population? What factors are associated with having your first child older or younger? 15 | 16 | Let's find out. 17 | 18 | 19 | 20 | 21 | 22 | ## Data 23 | We look at these questions using data from the National Survey of Sexual Attitudes and Lifestyles (NATSAL). More information about natsal and why it's cool. 24 | 25 | 26 | ## Coding in R 27 | To analyse the data we will use some thing called R. R is a computer language often used to explore data but you can use it for other things to, such as building websites or games. This may go in an introduction somewhere else. TBC. 28 | 29 | You don't need to know R to use this resource, you just need to be able to run the code by pressing a button. However, at the end you have the option to adjust the code to create your own analysis. 30 | 31 | Let's give it a go. This code opens the data and prints a list of variables. 32 | 33 | 34 | This is a hint. 35 | 36 | 37 | 38 | 39 | 40 | 41 | The distribution of age at birth of first child 42 | The data set contains a variable named dageCh1 which records age at birth of first child. 43 | 44 | *What's a variable? A variable is....* 45 | 46 | We can visualise the distribution of values for this variable using a histogram. We can also get descriptive statistics such as mean and standard deviation. Together, we can use this information to understand when people tend to have their first child. 47 | 48 | 49 | Hint space 50 | 51 | 52 | Questions about the results 53 | 54 | 55 | 56 | 57 | We can also look at differences between men and women. Usually fathers are older than mothers, so we should expect to find the mean is higher for women and men. Run the code to find out. 58 | 59 | We can calculate means and we can plot them using a bar chart. 60 | 61 | 62 | Hint space 63 | 64 | 65 | Question - On average, are men or women older at the age of their first child? 66 | 67 | 68 | 69 | This is not the correct answer. 70 | 71 | 72 | Good job! 73 | 74 | 75 | 76 | could extend this bit to include confidence intervals or a t-test. 77 | -------------------------------------------------------------------------------- /src/components/seo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Helmet from 'react-helmet' 3 | import { StaticQuery, graphql } from 'gatsby' 4 | 5 | const SEO = ({ title, description }) => ( 6 | { 9 | const lang = 'en' 10 | const siteMetadata = data.site.siteMetadata 11 | const pageTitle = title 12 | ? `${title} · ${siteMetadata.title}` 13 | : `${siteMetadata.title} · ${siteMetadata.slogan}` 14 | const pageDesc = description || siteMetadata.description 15 | const image = `${siteMetadata.siteUrl}/social.jpg` 16 | const meta = [ 17 | { 18 | name: 'description', 19 | content: pageDesc, 20 | }, 21 | { 22 | property: 'og:title', 23 | content: pageTitle, 24 | }, 25 | { 26 | property: 'og:description', 27 | content: pageDesc, 28 | }, 29 | { 30 | property: 'og:type', 31 | content: `website`, 32 | }, 33 | { 34 | property: 'og:site_name', 35 | content: siteMetadata.title, 36 | }, 37 | { 38 | property: 'og:image', 39 | content: image, 40 | }, 41 | { 42 | name: 'twitter:card', 43 | content: 'summary_large_image', 44 | }, 45 | { 46 | name: 'twitter:image', 47 | content: image, 48 | }, 49 | { 50 | name: 'twitter:creator', 51 | content: `@${siteMetadata.twitter}`, 52 | }, 53 | { 54 | name: 'twitter:site', 55 | content: `@${siteMetadata.twitter}`, 56 | }, 57 | { 58 | name: 'twitter:title', 59 | content: pageTitle, 60 | }, 61 | { 62 | name: 'twitter:description', 63 | content: pageDesc, 64 | }, 65 | ] 66 | 67 | return ( 68 | 69 | {siteMetadata.fonts && ( 70 | 74 | )} 75 | 76 | ) 77 | }} 78 | /> 79 | ) 80 | 81 | export default SEO 82 | 83 | const query = graphql` 84 | query DefaultSEOQuery { 85 | site { 86 | siteMetadata { 87 | title 88 | description 89 | slogan 90 | siteUrl 91 | twitter 92 | fonts 93 | } 94 | } 95 | } 96 | ` 97 | -------------------------------------------------------------------------------- /src/styles/slides.module.sass: -------------------------------------------------------------------------------- 1 | .root 2 | position: relative 3 | width: 100% 4 | height: 600px 5 | overflow: hidden 6 | 7 | .slides 8 | text-align: left !important 9 | 10 | \:global 11 | .reveal 12 | h1 13 | border-bottom: 1px solid var(--color-subtle-medium) 14 | 15 | h1, h2, h3, pre, ul, ol 16 | margin-bottom: 3rem 17 | 18 | li 19 | margin-left: 2rem 20 | 21 | img 22 | margin-bottom: 2rem 23 | 24 | img[align] 25 | padding: 2rem 26 | 27 | td 28 | border-bottom: 1px solid var(--color-subtle-medium) 29 | 30 | th 31 | border-bottom: 2px solid var(--color-theme) 32 | 33 | a 34 | color: var(--color-theme) 35 | text-decoration: underline 36 | 37 | section 38 | height: 100% 39 | padding: 1rem 40 | 41 | section:first-child h1, 42 | section:last-child h1 43 | font-size: 4rem 44 | border: 0 45 | text-align: center 46 | width: 100% 47 | height: 100% 48 | display: flex 49 | align-items: center 50 | justify-content: center 51 | 52 | pre 53 | margin-bottom: 1.5rem 54 | 55 | pre code 56 | width: 100% 57 | display: block 58 | padding: 0.5rem 1rem !important 59 | border-radius: var(--border-radius) 60 | background: var(--syntax-background) 61 | color: var(--syntax-text) 62 | position: relative 63 | 64 | pre code.language-out 65 | background: var(--syntax-text) 66 | color: var(--syntax-background) 67 | 68 | &:before 69 | content: "Output" 70 | position: absolute 71 | top: 0.5rem 72 | right: 1rem 73 | font-family: var(--font-primary) 74 | text-transform: uppercase 75 | font-weight: bold 76 | font-size: var(--font-size-xs) 77 | 78 | .reveal.show-notes 79 | max-width: 70% 80 | 81 | .reveal .speaker-notes 82 | background: var(--color-back) 83 | border: 2px solid var(--color-subtle-medium) 84 | color: var(--color-subtle-dark) 85 | font-size: var(--font-size-xs) 86 | font-family: var(--font-primary) 87 | border-radius: var(--border-radius) 88 | width: 40% 89 | margin-left: 2.75% 90 | height: 100% 91 | overflow: auto 92 | 93 | p 94 | margin-bottom: 1.5rem 95 | 96 | &:before 97 | content: "Script" 98 | font-weight: bold 99 | color: inherit 100 | opacity: 1 101 | 102 | @media(max-width: 1024px) 103 | .reveal.show-notes 104 | max-width: 100% 105 | 106 | .reveal.show-notes .speaker-notes 107 | width: 100% 108 | margin-left: 0 109 | top: 125% 110 | overflow: auto 111 | 112 | @media(max-width: 600px) 113 | .reveal.show-notes .speaker-notes 114 | height: auto 115 | top: 100% 116 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | const meta = require('./meta.json') 2 | const autoprefixer = require('autoprefixer') 3 | 4 | module.exports = { 5 | siteMetadata: meta, 6 | plugins: [ 7 | { 8 | resolve: `gatsby-plugin-sass`, 9 | options: { 10 | indentedSyntax: true, 11 | postCssPlugins: [autoprefixer()], 12 | cssLoaderOptions: { 13 | localIdentName: 14 | process.env.NODE_ENV == 'development' 15 | ? '[name]-[local]-[hash:8]' 16 | : '[hash:8]', 17 | }, 18 | }, 19 | }, 20 | `gatsby-plugin-react-helmet`, 21 | { 22 | resolve: `gatsby-source-filesystem`, 23 | options: { 24 | name: `chapters`, 25 | path: `${__dirname}/chapters`, 26 | }, 27 | }, 28 | { 29 | resolve: `gatsby-source-filesystem`, 30 | options: { 31 | name: `slides`, 32 | path: `${__dirname}/slides`, 33 | }, 34 | }, 35 | { 36 | resolve: `gatsby-source-filesystem`, 37 | options: { 38 | name: `exercises`, 39 | path: `${__dirname}/exercises`, 40 | }, 41 | }, 42 | { 43 | resolve: 'gatsby-plugin-react-svg', 44 | options: { 45 | rule: { 46 | include: /static/, 47 | }, 48 | }, 49 | }, 50 | { 51 | resolve: `gatsby-transformer-remark`, 52 | options: { 53 | plugins: [ 54 | `gatsby-remark-copy-linked-files`, 55 | { 56 | resolve: `gatsby-remark-prismjs`, 57 | options: { 58 | noInlineHighlight: true, 59 | }, 60 | }, 61 | { 62 | resolve: `gatsby-remark-smartypants`, 63 | options: { 64 | dashes: 'oldschool', 65 | }, 66 | }, 67 | { 68 | resolve: `gatsby-remark-images`, 69 | options: { 70 | maxWidth: 790, 71 | linkImagesToOriginal: true, 72 | sizeByPixelDensity: false, 73 | showCaptions: true, 74 | quality: 80, 75 | withWebp: { quality: 80 }, 76 | }, 77 | }, 78 | `gatsby-remark-unwrap-images`, 79 | ], 80 | }, 81 | }, 82 | `gatsby-transformer-sharp`, 83 | `gatsby-plugin-sharp`, 84 | `gatsby-plugin-sitemap`, 85 | { 86 | resolve: `gatsby-plugin-manifest`, 87 | options: { 88 | name: meta.title, 89 | short_name: meta.title, 90 | start_url: `/`, 91 | background_color: meta.theme, 92 | theme_color: meta.theme, 93 | display: `minimal-ui`, 94 | icon: `static/favi.png`, 95 | }, 96 | }, 97 | `gatsby-plugin-offline`, 98 | ], 99 | } 100 | -------------------------------------------------------------------------------- /static/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | 14 | 19 | 21 | 25 | 30 | 33 | 38 | 42 | 44 | 49 | 52 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/components/slides.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StaticQuery, graphql } from 'gatsby' 3 | import Marked from 'reveal.js/plugin/markdown/marked.js' 4 | import classNames from 'classnames' 5 | 6 | import '../styles/reveal.css' 7 | import classes from '../styles/slides.module.sass' 8 | 9 | function getFiles({ allMarkdownRemark }) { 10 | return Object.assign( 11 | {}, 12 | ...allMarkdownRemark.edges.map(({ node }) => ({ 13 | [node.fields.slug.replace('/', '')]: node.rawMarkdownBody, 14 | })) 15 | ) 16 | } 17 | 18 | function getSlideContent(data, source) { 19 | const files = getFiles(data) 20 | const file = files[source] || '' 21 | return file.split('\n---\n').map(f => f.trim()) 22 | } 23 | 24 | class Slides extends React.Component { 25 | componentDidMount() { 26 | import('reveal.js').then(({ default: Reveal }) => { 27 | window.Reveal = Reveal 28 | window.marked = Marked 29 | import('reveal.js/plugin/markdown/markdown.js').then(({ RevealMarkdown }) => { 30 | RevealMarkdown.init() 31 | Reveal.initialize({ 32 | center: false, 33 | progress: false, 34 | showNotes: true, 35 | controls: true, 36 | width: '100%', 37 | height: 600, 38 | minScale: 0.75, 39 | maxScale: 1, 40 | }) 41 | }) 42 | }) 43 | } 44 | 45 | componentWillUnmount() { 46 | // Work around default reveal.js behaviour that doesn't allow 47 | // re-initialization and clashes with React 48 | delete window.Reveal 49 | delete window.marked 50 | delete require.cache[require.resolve('reveal.js')] 51 | delete require.cache[require.resolve('reveal.js/plugin/markdown/markdown.js')] 52 | } 53 | 54 | render() { 55 | const { source } = this.props 56 | const revealClassNames = classNames('reveal', 'show-notes', classes.reveal) 57 | const slideClassNames = classNames('slides', classes.slides) 58 | 59 | return ( 60 |
    61 |
    62 | { 80 | const content = getSlideContent(data, source) 81 | return ( 82 |
    83 | {content.map((markdown, i) => ( 84 |
    89 |