diff --git a/.nojekyll b/.nojekyll index 36726aa..ac1a7c3 100644 --- a/.nojekyll +++ b/.nojekyll @@ -1 +1 @@ -94e01f35 \ No newline at end of file +c115ff76 \ No newline at end of file diff --git a/404.html b/404.html index 8357655..4b7f364 100644 --- a/404.html +++ b/404.html @@ -7,7 +7,7 @@ -quarto-input90fa573d2285652 – EMSE 6035 +quarto-inputafaef6fd2acfad06 – EMSE 6035 + + + + + + + + diff --git a/class/2-data-wrangling/libs/clipboard/clipboard.min.js b/class/2-data-wrangling/libs/clipboard/clipboard.min.js new file mode 100644 index 0000000..28650f3 --- /dev/null +++ b/class/2-data-wrangling/libs/clipboard/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.6 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return o={},r.m=n=[function(t,e){t.exports=function(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){var o=this;function r(){o.off(t,r),e.apply(n,arguments)}return r._=e,this.on(t,r,n)},emit:function(t){for(var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;o noup * 2 && secs % noup > 0) { return; } + } + + // should we apply or remove warning class? + warnwhen = timer.div.dataset.warnwhen; + if (warnwhen && warnwhen > 0) { + if (secs <= warnwhen && !timer.div.classList.contains("warning")) { + timer.div.classList.add("warning"); + } else if (secs > warnwhen && timer.div.classList.contains("warning")) { + timer.div.classList.remove("warning"); + } + } + + var mins = Math.floor(secs / 60); // 1 min = 60 secs + secs -= mins * 60; + + // Update HTML + timer.min.innerHTML = String(mins).padStart(2, 0); + timer.sec.innerHTML = String(secs).padStart(2, 0); +} +var countdown = function (e) { + target = e.target; + if (target.classList.contains("countdown-digits")) { + target = target.parentElement; + } + if (target.tagName == "CODE") { + target = target.parentElement; + } + + // Init counter + if (!counters.timer.hasOwnProperty(target.id)) { + counters.timer[target.id] = {}; + // Set the containers + counters.timer[target.id].min = target.getElementsByClassName("minutes")[0]; + counters.timer[target.id].sec = target.getElementsByClassName("seconds")[0]; + counters.timer[target.id].div = target; + } + + if (!counters.timer[target.id].running) { + if (!counters.timer[target.id].end) { + counters.timer[target.id].end = parseInt(counters.timer[target.id].min.innerHTML) * 60; + counters.timer[target.id].end += parseInt(counters.timer[target.id].sec.innerHTML); + } + + counters.timer[target.id].value = counters.timer[target.id].end; + update_timer(counters.timer[target.id]); + if (counters.ticker) counters.timer[target.id].value += 1; + + // Start if not past end date + if (counters.timer[target.id].value > 0) { + base_class = target.className.replace(/\s?(running|finished)/, "") + target.className = base_class + " running"; + counters.timer[target.id].running = true; + + if (!counters.ticker) { + counters.ticker = setInterval(counter_update_all, 1000); + } + } + } else { + // Bump timer value if running & clicked + counters.timer[target.id].value += counter_bump_increment(counters.timer[target.id].end); + update_timer(counters.timer[target.id], force = true); + counters.timer[target.id].value += 1; + } +}; + +var counter_bump_increment = function(val) { + if (val <= 30) { + return 5; + } else if (val <= 300) { + return 15; + } else if (val <= 3000) { + return 30; + } else { + return 60; + } +} + +var counter_update_all = function() { + // Iterate over all running timers + for (var i in counters.timer) { + // Stop if passed end time + console.log(counters.timer[i].id) + counters.timer[i].value--; + if (counters.timer[i].value <= 0) { + counters.timer[i].min.innerHTML = "00"; + counters.timer[i].sec.innerHTML = "00"; + counters.timer[i].div.className = counters.timer[i].div.className.replace("running", "finished"); + counters.timer[i].running = false; + } else { + // Update + update_timer(counters.timer[i]); + + // Play countdown sound if data-audio=true on container div + let audio = counters.timer[i].div.dataset.audio + if (audio && counters.timer[i].value == 5) { + counter_play_sound(audio); + } + } + } + + // If no more running timers, then clear ticker + var timerIsRunning = false; + for (var t in counters.timer) { + timerIsRunning = timerIsRunning || counters.timer[t].running + } + if (!timerIsRunning) { + clearInterval(counters.ticker); + counters.ticker = null; + } +} + +var counter_play_sound = function(url) { + if (typeof url === 'boolean') { + url = 'libs/countdown/smb_stage_clear.mp3'; + } + sound = new Audio(url); + sound.play(); +} + +var counter_addEventListener = function() { + if (!document.getElementsByClassName("countdown").length) { + setTimeout(counter_addEventListener, 2); + return; + } + var counter_divs = document.getElementsByClassName("countdown"); + console.log(counter_divs); + for (var i = 0; i < counter_divs.length; i++) { + counter_divs[i].addEventListener("click", countdown, false); + } +}; + +counter_addEventListener(); diff --git a/class/2-data-wrangling/libs/countdown/smb_stage_clear.mp3 b/class/2-data-wrangling/libs/countdown/smb_stage_clear.mp3 new file mode 100644 index 0000000..da2ddc2 Binary files /dev/null and b/class/2-data-wrangling/libs/countdown/smb_stage_clear.mp3 differ diff --git a/class/2-data-wrangling/libs/header-attrs/header-attrs.js b/class/2-data-wrangling/libs/header-attrs/header-attrs.js new file mode 100644 index 0000000..dd57d92 --- /dev/null +++ b/class/2-data-wrangling/libs/header-attrs/header-attrs.js @@ -0,0 +1,12 @@ +// Pandoc 2.9 adds attributes on both header and div. We remove the former (to +// be compatible with the behavior of Pandoc < 2.8). +document.addEventListener('DOMContentLoaded', function(e) { + var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); + var i, h, a; + for (i = 0; i < hs.length; i++) { + h = hs[i]; + if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 + a = h.attributes; + while (a.length > 0) h.removeAttribute(a[0].name); + } +}); diff --git a/class/2-data-wrangling/libs/panelset/panelset.css b/class/2-data-wrangling/libs/panelset/panelset.css new file mode 100644 index 0000000..6665f55 --- /dev/null +++ b/class/2-data-wrangling/libs/panelset/panelset.css @@ -0,0 +1,227 @@ +/* prefixed by https://autoprefixer.github.io (PostCSS: v7.0.23, autoprefixer: v9.7.3) */ + +.panelset { + width: 100%; + position: relative; + --panel-tabs-border-bottom: #ddd; + --panel-tabs-sideways-max-width: 25%; + --panel-tab-foreground: currentColor; + --panel-tab-background: unset; + --panel-tab-active-foreground: currentColor; + --panel-tab-active-background: unset; + --panel-tab-hover-foreground: currentColor; + --panel-tab-hover-background: unset; + --panel-tab-active-border-color: currentColor; + --panel-tab-hover-border-color: currentColor; + --panel-tab-inactive-opacity: 0.5; + --panel-tab-font-family: inherit; +} + +.panelset * { + box-sizing: border-box; +} + +.panelset .panel-tabs { + display: -webkit-box; + display: flex; + flex-wrap: wrap; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + flex-direction: row; + -webkit-box-pack: start; + justify-content: start; + -webkit-box-align: center; + align-items: center; + overflow-y: visible; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + padding: 0 0 2px 0; + box-shadow: inset 0 -2px 0px var(--panel-tabs-border-bottom); +} + +.panelset .panel-tabs * { + -webkit-transition: opacity 0.5s ease; + transition: opacity 0.5s ease; +} + +.panelset .panel-tabs .panel-tab { + min-height: 50px; + display: -webkit-box; + display: flex; + -webkit-box-pack: center; + justify-content: center; + -webkit-box-align: center; + align-items: center; + padding: 0.5em 1em; + font-family: var(--panel-tab-font-family); + opacity: var(--panel-tab-inactive-opacity); + border-top: 2px solid transparent; + border-bottom: 2px solid transparent; + margin-bottom: -2px; + color: var(--panel-tab-foreground); + background-color: var(--panel-tab-background); + list-style: none; + z-index: 5; +} + +.panelset .panel-tabs .panel-tab > a { + color: currentColor; + text-decoration: none; + border: none; + width: 100%; + height: 100%; +} + +.panelset .panel-tabs .panel-tab > a:focus { + outline: none; + text-decoration: none; + border: none; +} + +.panelset .panel-tabs .panel-tab > a:hover { + text-decoration: none; + border: none; +} + +.panelset .panel-tabs .panel-tab:hover { + border-bottom-color: var(--panel-tab-hover-border-color); + color: var(--panel-tab-hover-foreground); + background-color: var(--panel-tab-hover-background); + opacity: 1; + cursor: pointer; + z-index: 10; +} + +.panelset .panel-tabs .panel-tab:focus { + outline: none; + color: var(--panel-tab-hover-foreground); + border-bottom-color: var(--panel-tab-hover-border-color); + background-color: var(--panel-tab-hover-background); +} + +.panelset .panel-tabs .panel-tab.panel-tab-active { + border-top-color: var(--panel-tab-active-border-color); + color: var(--panel-tab-active-foreground); + background-color: var(--panel-tab-active-background); + opacity: 1; +} + +.panelset .panel { + display: none; +} + +.panelset .panel-active { + display: block; +} + +/* ---- Sideways Panelset ---- */ + +@media (min-width: 480px) { + .panelset.sideways { + display: flex; + flex-direction: row; + } + + .panelset.sideways .panel-tabs { + box-shadow: none; + flex-direction: column; + align-items: start; + margin: 0; + margin-right: 1em; + border-right: 2px solid var(--panel-tabs-border-bottom); + max-width: var(--panel-tabs-sideways-max-width); + } + + .panelset.sideways .panel { + max-width: calc(100% - var(--panel-tabs-sideways-max-width) - 1em); + } + + .panelset.sideways .panel-tabs .panel-tab { + border-top: unset; + border-bottom: unset; + padding-left: 0; + } + + .panelset.sideways.right { + flex-direction: row-reverse; + justify-content: space-between; + } + + .panelset.sideways.right { + text-align: inherit; + } + + .panelset.sideways.right .panel-tabs { + align-items: end; + margin-right: 0; + margin-left: 1em; + border-right: unset; + border-left: 2px solid var(--panel-tabs-border-bottom); + } + + .panelset.sideways.right .panel-tabs .panel-tab { + padding-left: 1em; + width: 100%; + } + + .panelset.sideways.right .panel-tabs .panel-tab a { + text-align: right; + } +} + +/* + This next part repeats the same CSS inside the @media query above but with + remarkjs-specific classes to ensure that sideways panelsets are always used. + In the future, we could use @container queries instead once they're availble. +*/ + +.remark-container .panelset.sideways { + display: flex; + flex-direction: row; +} + +.remark-container .panelset.sideways .panel-tabs { + box-shadow: none; + flex-direction: column; + align-items: start; + margin: 0; + margin-right: 1em; + border-right: 2px solid var(--panel-tabs-border-bottom); + max-width: var(--panel-tabs-sideways-max-width); +} + +.remark-container .panelset.sideways .panel { + max-width: calc(100% - var(--panel-tabs-sideways-max-width) - 1em); +} + +.remark-container .panelset.sideways .panel-tabs .panel-tab { + border-top: unset; + border-bottom: unset; + padding-left: 0; +} + +.remark-container .panelset.sideways.right { + flex-direction: row-reverse; + justify-content: space-between; +} + +.remark-container .panelset.sideways.right { + text-align: inherit; +} + +.remark-container .panelset.sideways.right .panel-tabs { + align-items: end; + margin-right: 0; + margin-left: 1em; + border-right: unset; + border-left: 2px solid var(--panel-tabs-border-bottom); +} + +.remark-container .panelset.sideways.right .panel-tabs .panel-tab { + padding-left: 1em; + width: 100%; +} + +.remark-container .panelset.sideways.right .panel-tabs .panel-tab a { + text-align: right; +} diff --git a/class/2-data-wrangling/libs/panelset/panelset.js b/class/2-data-wrangling/libs/panelset/panelset.js new file mode 100644 index 0000000..730d54d --- /dev/null +++ b/class/2-data-wrangling/libs/panelset/panelset.js @@ -0,0 +1,325 @@ +/* global slideshow */ +(function () { + const ready = function (fn) { + /* MIT License Copyright (c) 2016 Nuclei */ + /* https://github.com/nuclei/readyjs */ + const completed = () => { + document.removeEventListener('DOMContentLoaded', completed) + window.removeEventListener('load', completed) + fn() + } + if (document.readyState !== 'loading') { + setTimeout(fn) + } else { + document.addEventListener('DOMContentLoaded', completed) + window.addEventListener('load', completed) + } + } + + ready(function () { + [...document.querySelectorAll('.panel-name')] + .map(el => el.textContent.trim()) + + const panelIds = {} + + const uniquePanelId = (name) => { + name = encodeURIComponent(name.toLowerCase().replace(/[\s]/g, '-')) + if (Object.keys(panelIds).includes(name)) { + name += ++panelIds[name] + } else { + panelIds[name] = 1 + } + return name + } + + const identifyPanelName = (item) => { + let name = 'Panel' + + // If the item doesn't have a parent element, then we've already processed + // it, probably because we're in an Rmd, and it's been removed from the DOM + if (!item.parentElement) { + return + } + + // In R Markdown when header-attrs.js is present, we may have found a + // section header but the class attributes won't be duplicated on the tag + if ( + (item.tagName === 'SECTION' || item.classList.contains('section')) && + /^H[1-6]/.test(item.children[0].tagName) + ) { + name = item.children[0].textContent + item.classList.remove('panel-name') + item.removeChild(item.children[0]) + return name + } + + const nameDiv = item.querySelector('.panel-name') + if (!nameDiv) return name + + // In remarkjs the .panel-name span might be in a paragraph tag + // and if the

is empty, we'll remove it + if ( + nameDiv.tagName === 'SPAN' && + nameDiv.parentNode.tagName === 'P' && + nameDiv.textContent === nameDiv.parentNode.textContent + ) { + name = nameDiv.textContent + item.removeChild(nameDiv.parentNode) + return name + } + + // If none of the above, remove the nameDiv and return the name + name = nameDiv.textContent + nameDiv.parentNode.removeChild(nameDiv) + return name + } + + const processPanelItem = (item) => { + const name = identifyPanelName(item) + if (!name) { + return null + } + return { name, content: item.children, id: uniquePanelId(name) } + } + + const getCurrentPanelFromUrl = (panelset) => { + const params = new URLSearchParams(window.location.search) + return params.get(panelset) + } + + const reflowPanelSet = (panels, idx) => { + const res = document.createElement('div') + res.className = 'panelset' + res.id = 'panelset' + (idx > 0 ? idx : '') + const panelSelected = getCurrentPanelFromUrl(res.id) + + // create header row + const headerRow = document.createElement('ul') + headerRow.className = 'panel-tabs' + headerRow.setAttribute('role', 'tablist') + panels + .map((p, idx) => { + const panelHeaderItem = document.createElement('li') + panelHeaderItem.className = 'panel-tab' + panelHeaderItem.setAttribute('role', 'tab') + const thisPanelIsActive = panelSelected ? panelSelected === p.id : idx === 0 + if (thisPanelIsActive) { + panelHeaderItem.classList.add('panel-tab-active') + panelHeaderItem.setAttribute('aria-selected', true) + } + panelHeaderItem.tabIndex = 0 + panelHeaderItem.id = res.id + '_' + p.id // #panelsetid_panelid + + const panelHeaderLink = document.createElement('a') + panelHeaderLink.href = '?' + res.id + '=' + p.id + '#' + panelHeaderItem.id + panelHeaderLink.setAttribute('onclick', 'return false;') + panelHeaderLink.tabIndex = -1 // list item is tabable, not link + panelHeaderLink.innerHTML = p.name + panelHeaderLink.setAttribute('aria-controls', p.id) + + panelHeaderItem.appendChild(panelHeaderLink) + return panelHeaderItem + }) + .forEach(el => headerRow.appendChild(el)) + + res.appendChild(headerRow) + + panels + .map((p, idx) => { + const panelContent = document.createElement('section') + panelContent.className = 'panel' + panelContent.setAttribute('role', 'tabpanel') + const thisPanelIsActive = panelSelected ? panelSelected === p.id : idx === 0 + panelContent.classList.toggle('panel-active', thisPanelIsActive) + panelContent.id = p.id + panelContent.setAttribute('aria-labelledby', p.id) + Array.from(p.content).forEach(el => panelContent.appendChild(el)) + return panelContent + }) + .forEach(el => res.appendChild(el)) + + return res + } + + /* + * Update selected panel for panelset or delete panelset from query string + * + * @param panelset Panelset ID to update in the search params + * @param panel Panel ID of selected panel in panelset, or null to delete from search params + * @param params Current params object, or params from window.location.search + */ + function updateSearchParams (panelset, panel, params = new URLSearchParams(window.location.search)) { + if (panel) { + params.set(panelset, panel) + } else { + params.delete(panelset) + } + return params + } + + /* + * Update the URL to match params + */ + const updateUrl = (params) => { + if (typeof params === 'undefined') return + params = params.toString() ? ('?' + params.toString()) : '' + const { pathname, hash } = window.location + const uri = pathname + params + hash + window.history.replaceState(uri, '', uri) + } + + const togglePanel = (clicked) => { + if (clicked.nodeName.toUpperCase() === 'A') { + clicked = clicked.parentElement + } + if (!clicked.classList.contains('panel-tab')) return + if (clicked.classList.contains('panel-tab-active')) return + + const tabs = clicked.parentNode + .querySelectorAll('.panel-tab') + const panels = clicked.parentNode.parentNode + .querySelectorAll('.panel') + const panelTabClicked = clicked.children[0].getAttribute('aria-controls') + const panelClicked = clicked.parentNode.parentNode.id + + Array.from(tabs) + .forEach(t => { + t.classList.remove('panel-tab-active') + t.removeAttribute('aria-selected') + }) + Array.from(panels) + .forEach(p => { + const active = p.id === panelTabClicked + p.classList.toggle('panel-active', active) + // make inactive panels inaccessible by keyboard navigation + if (active) { + p.removeAttribute('tabIndex') + p.removeAttribute('aria-hidden') + } else { + p.setAttribute('tabIndex', -1) + p.setAttribute('aria-hidden', true) + } + }) + + clicked.classList.add('panel-tab-active') + clicked.setAttribute('aria-selected', true) + + // emit window resize event to trick html widgets into fitting to the panel width + window.dispatchEvent(new Event('resize')) + + // update query string + const params = updateSearchParams(panelClicked, panelTabClicked) + updateUrl(params) + } + + const initPanelSet = (panelset, idx) => { + let panels = Array.from(panelset.querySelectorAll('.panel')) + if (!panels.length && panelset.matches('.section[class*="level"]')) { + // we're in tabset-alike R Markdown + const panelsetLevel = [...panelset.classList] + .filter(s => s.match(/^level/))[0] + .replace('level', '') + + // move children that aren't inside a section up above the panelset + Array.from(panelset.children).forEach(function (el) { + if (el.matches('div.section[class*="level"]')) return + panelset.parentElement.insertBefore(el, panelset) + }) + + // panels are all .sections with .level + const panelLevel = +panelsetLevel + 1 + panels = Array.from(panelset.querySelectorAll(`.section.level${panelLevel}`)) + } + + if (!panels.length) return + + const contents = panels.map(processPanelItem).filter(o => o !== null) + const newPanelSet = reflowPanelSet(contents, idx) + newPanelSet.classList = panelset.classList + panelset.parentNode.insertBefore(newPanelSet, panelset) + panelset.parentNode.removeChild(panelset) + + // click and touch events + const panelTabs = newPanelSet.querySelector('.panel-tabs'); + ['click', 'touchend'].forEach(eventType => { + panelTabs.addEventListener(eventType, function (ev) { + togglePanel(ev.target) + ev.stopPropagation() + }) + }) + panelTabs.addEventListener('touchmove', function (ev) { + ev.preventDefault() + }) + + // key events + newPanelSet + .querySelector('.panel-tabs') + .addEventListener('keydown', (ev) => { + const self = ev.currentTarget.querySelector('.panel-tab-active') + if (ev.code === 'Space' || ev.code === 'Enter') { + togglePanel(ev.target) + ev.stopPropagation() + } else if (ev.code === 'ArrowLeft' && self.previousSibling) { + togglePanel(self.previousSibling) + self.previousSibling.focus() + ev.stopPropagation() + } else if (ev.code === 'ArrowRight' && self.nextSibling) { + togglePanel(self.nextSibling) + self.nextSibling.focus() + ev.stopPropagation() + } + }) + + return panels + } + + // initialize panels + Array.from(document.querySelectorAll('.panelset')).map(initPanelSet) + + if (typeof slideshow !== 'undefined') { + const getVisibleActivePanelInfo = () => { + const slidePanels = document.querySelectorAll('.remark-visible .panel-tab-active') + + if (!slidePanels.length) return null + + return slidePanels.map(panel => { + return { + panel, + panelId: panel.children[0].getAttribute('aria-controls'), + panelSetId: panel.parentNode.parentNode.id + } + }) + } + + slideshow.on('hideSlide', slide => { + // clear focus if we had a panel-tab selected + document.activeElement.blur() + + // clear search query for panelsets in current slide + const params = [...document.querySelectorAll('.remark-visible .panelset')] + .reduce(function (params, panelset) { + return updateSearchParams(panelset.id, null, params) + }, new URLSearchParams(window.location.search)) + + updateUrl(params) + }) + + slideshow.on('afterShowSlide', slide => { + const slidePanels = getVisibleActivePanelInfo() + + if (slidePanels) { + // only first panel gets focus + slidePanels[0].panel.focus() + // but still update the url to reflect all active panels + const params = slidePanels.reduce( + function (params, { panelId, panelSetId }) { + return updateSearchParams(panelSetId, panelId, params) + }, + new URLSearchParams(window.location.search) + ) + updateUrl(params) + } + }) + } + }) +})() diff --git a/class/2-data-wrangling/libs/remark-css/default.css b/class/2-data-wrangling/libs/remark-css/default.css new file mode 100644 index 0000000..d37bfd2 --- /dev/null +++ b/class/2-data-wrangling/libs/remark-css/default.css @@ -0,0 +1,72 @@ +a, a > code { + color: rgb(249, 38, 114); + text-decoration: none; +} +.footnote { + position: absolute; + bottom: 3em; + padding-right: 4em; + font-size: 90%; +} +.remark-code-line-highlighted { background-color: #ffff88; } + +.inverse { + background-color: #272822; + color: #d6d6d6; + text-shadow: 0 0 20px #333; +} +.inverse h1, .inverse h2, .inverse h3 { + color: #f3f3f3; +} +/* Two-column layout */ +.left-column { + color: #777; + width: 20%; + height: 92%; + float: left; +} +.left-column h2:last-of-type, .left-column h3:last-child { + color: #000; +} +.right-column { + width: 75%; + float: right; + padding-top: 1em; +} +.pull-left { + float: left; + width: 47%; +} +.pull-right { + float: right; + width: 47%; +} +.pull-right + * { + clear: both; +} +img, video, iframe { + max-width: 100%; +} +blockquote { + border-left: solid 5px lightgray; + padding-left: 1em; +} +.remark-slide table { + margin: auto; + border-top: 1px solid #666; + border-bottom: 1px solid #666; +} +.remark-slide table thead th { border-bottom: 1px solid #ddd; } +th, td { padding: 5px; } +.remark-slide thead, .remark-slide tfoot, .remark-slide tr:nth-child(even) { background: #eee } + +@page { margin: 0; } +@media print { + .remark-slide-scaler { + width: 100% !important; + height: 100% !important; + transform: scale(1) !important; + top: 0 !important; + left: 0 !important; + } +} diff --git a/class/2-data-wrangling/libs/shareon/shareon.min.css b/class/2-data-wrangling/libs/shareon/shareon.min.css new file mode 100644 index 0000000..29ebe89 --- /dev/null +++ b/class/2-data-wrangling/libs/shareon/shareon.min.css @@ -0,0 +1,5 @@ +/*! + * shareon v1.4.1 by Nikita Karamov + * https://shareon.js.org + */ +.shareon{font-size:0!important}.shareon>*{display:inline-block;position:relative;height:24px;min-width:16px;margin:3px;padding:6px 10px;background-color:#ccc;border-radius:3.33333px;border:none;box-sizing:content-box;color:#fff;line-height:1.5;transition:opacity .3s ease;vertical-align:middle}.shareon>:hover{border:none;cursor:pointer;opacity:.7}.shareon>:not(:empty){font-size:16px;text-decoration:none}.shareon>:not(:empty):before{position:relative;height:100%;width:28px;top:0;left:0;background-position:0 50%}.shareon>:before{display:inline-block;position:absolute;height:20px;width:20px;top:8px;left:8px;background-repeat:no-repeat;background-size:20px 20px;content:"";vertical-align:bottom}.shareon>.facebook{background-color:#1877f2}.shareon>.facebook:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M23.998 12c0-6.628-5.372-12-11.999-12C5.372 0 0 5.372 0 12c0 5.988 4.388 10.952 10.124 11.852v-8.384H7.078v-3.469h3.046V9.356c0-3.008 1.792-4.669 4.532-4.669 1.313 0 2.686.234 2.686.234v2.953H15.83c-1.49 0-1.955.925-1.955 1.874V12h3.328l-.532 3.469h-2.796v8.384c5.736-.9 10.124-5.864 10.124-11.853z'/%3E%3C/svg%3E")}.shareon>.linkedin{background-color:#2867b2}.shareon>.linkedin:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M23.722 23.72h-4.91v-7.692c0-1.834-.038-4.194-2.559-4.194-2.56 0-2.95 1.995-2.95 4.06v7.827H8.394V7.902h4.716v2.157h.063c.659-1.244 2.261-2.556 4.655-2.556 4.974 0 5.894 3.274 5.894 7.535zM.388 7.902h4.923v15.819H.388zM2.85 5.738A2.849 2.849 0 010 2.886a2.851 2.851 0 112.85 2.852z'/%3E%3C/svg%3E")}.shareon>.linkedin:not(:empty):before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z'/%3E%3C/svg%3E")}.shareon>.messenger{background-color:#09f}.shareon>.messenger:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 11.64C0 4.95 5.24 0 12 0s12 4.95 12 11.64-5.24 11.64-12 11.64c-1.21 0-2.38-.16-3.47-.46a.96.96 0 00-.64.05L5.5 23.92a.96.96 0 01-1.35-.85l-.07-2.14a.97.97 0 00-.32-.68A11.39 11.39 0 010 11.64zm8.32-2.19l-3.52 5.6c-.35.53.32 1.14.82.75l3.79-2.87c.26-.2.6-.2.87 0l2.8 2.1c.84.63 2.04.4 2.6-.48l3.52-5.6c.35-.53-.32-1.13-.82-.75l-3.79 2.87c-.25.2-.6.2-.86 0l-2.8-2.1a1.8 1.8 0 00-2.61.48z'/%3E%3C/svg%3E")}.shareon>.odnoklassniki{background-color:#ee8208}.shareon>.odnoklassniki:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M14.505 17.44a11.599 11.599 0 003.6-1.49 1.816 1.816 0 00-1.935-3.073 7.866 7.866 0 01-8.34 0 1.814 1.814 0 00-2.5.565c0 .002 0 .004-.002.005a1.812 1.812 0 00.567 2.5l.002.002c1.105.695 2.322 1.2 3.596 1.488l-3.465 3.465A1.796 1.796 0 006 23.439l.03.03c.344.354.81.53 1.274.53.465 0 .93-.176 1.275-.53L12 20.065l3.404 3.406a1.815 1.815 0 002.566-2.565l-3.465-3.466zM12 12.388a6.202 6.202 0 006.195-6.193C18.195 2.78 15.415 0 12 0S5.805 2.78 5.805 6.197A6.2 6.2 0 0012 12.389zm0-8.757a2.566 2.566 0 010 5.13 2.569 2.569 0 01-2.565-2.564A2.57 2.57 0 0112 3.63z'/%3E%3C/svg%3E")}.shareon>.pinterest{background-color:#ee0023}.shareon>.pinterest:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.017 0C5.396 0 .029 5.367.029 11.987c0 5.079 3.158 9.417 7.618 11.162-.105-.949-.199-2.403.041-3.439.219-.937 1.406-5.957 1.406-5.957s-.359-.72-.359-1.781c0-1.663.967-2.911 2.168-2.911 1.024 0 1.518.769 1.518 1.688 0 1.029-.653 2.567-.992 3.992-.285 1.193.6 2.165 1.775 2.165 2.128 0 3.768-2.245 3.768-5.487 0-2.861-2.063-4.869-5.008-4.869-3.41 0-5.409 2.562-5.409 5.199 0 1.033.394 2.143.889 2.741.099.12.112.225.085.345-.09.375-.293 1.199-.334 1.363-.053.225-.172.271-.401.165-1.495-.69-2.433-2.878-2.433-4.646 0-3.776 2.748-7.252 7.92-7.252 4.158 0 7.392 2.967 7.392 6.923 0 4.135-2.607 7.462-6.233 7.462-1.214 0-2.354-.629-2.758-1.379l-.749 2.848c-.269 1.045-1.004 2.352-1.498 3.146 1.123.345 2.306.535 3.55.535 6.607 0 11.985-5.365 11.985-11.987C23.97 5.39 18.592.026 11.985.026z'/%3E%3C/svg%3E")}.shareon>.pocket{background-color:#ef4154}.shareon>.pocket:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.813 10.259l-5.646 5.419a1.649 1.649 0 01-2.282 0l-5.646-5.419a1.645 1.645 0 012.276-2.376l4.511 4.322 4.517-4.322a1.643 1.643 0 012.326.049 1.64 1.64 0 01-.045 2.326zm5.083-7.546a2.163 2.163 0 00-2.041-1.436H2.179c-.9 0-1.717.564-2.037 1.405-.094.25-.142.511-.142.774v7.245l.084 1.441c.348 3.277 2.047 6.142 4.682 8.139.045.036.094.07.143.105l.03.023a11.899 11.899 0 004.694 2.072c.786.158 1.591.24 2.389.24.739 0 1.481-.067 2.209-.204.088-.029.176-.045.264-.06.023 0 .049-.015.074-.029a12.002 12.002 0 004.508-2.025l.029-.031.135-.105c2.627-1.995 4.324-4.862 4.686-8.148L24 10.678V3.445c0-.251-.031-.5-.121-.742z'/%3E%3C/svg%3E")}.shareon>.reddit{background-color:#ff4500}.shareon>.reddit:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M19.53 1.148a1.83 1.83 0 00-1.667 1.054l-4.372-.928a.522.522 0 00-.358.063.515.515 0 00-.21.297L11.59 7.925c-2.807.086-5.32.909-7.137 2.237a2.668 2.668 0 00-1.815-.737A2.64 2.64 0 000 12.064a2.634 2.634 0 001.563 2.407 4.95 4.95 0 00-.065.803c0 4.053 4.71 7.326 10.537 7.326s10.537-3.273 10.537-7.326a4.548 4.548 0 00-.063-.782 2.732 2.732 0 001.519-2.428 2.64 2.64 0 00-2.639-2.64 2.53 2.53 0 00-1.816.74c-1.796-1.288-4.287-2.134-7.031-2.239l1.204-5.637 3.906.823a1.888 1.888 0 001.878 1.777c1.024 0 1.87-.837 1.88-1.861a1.884 1.884 0 00-1.88-1.88zM7.907 18.066c-.13 0-.254.05-.347.141a.498.498 0 000 .697c1.266 1.267 3.736 1.373 4.454 1.373s3.167-.084 4.454-1.373a.546.546 0 00.044-.697.5.5 0 00-.698 0c-.823.802-2.533 1.099-3.779 1.099s-2.977-.295-3.779-1.099a.49.49 0 00-.349-.142zm-1.932-4.122c0-1.035.844-1.88 1.88-1.88 1.034 0 1.878.843 1.878 1.879S8.89 15.82 7.856 15.82a1.882 1.882 0 01-1.88-1.877zm10.155-1.88c1.035 0 1.88.845 1.88 1.879 0 1.035-.844 1.878-1.879 1.878s-1.879-.843-1.879-1.877c0-1.037.844-1.88 1.878-1.88z' fill-rule='evenodd'/%3E%3C/svg%3E")}.shareon>.telegram{background-color:#179cde}.shareon>.telegram:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1.517 11.31c-.962.382-1.466.756-1.512 1.122-.088.702.924.921 2.196 1.335 1.037.337 2.433.731 3.158.747.658.014 1.393-.257 2.204-.814 5.533-3.735 8.39-5.622 8.57-5.663.126-.029.301-.065.42.04.119.106.107.306.095.36-.101.429-5.3 5.156-5.599 5.467-1.143 1.187-2.443 1.913-.437 3.235 1.735 1.144 2.746 1.873 4.534 3.045 1.142.75 2.039 1.637 3.218 1.529.543-.05 1.104-.56 1.389-2.083.673-3.598 1.996-11.392 2.302-14.604a3.585 3.585 0 00-.034-.8c-.027-.158-.084-.383-.29-.55-.243-.197-.619-.24-.787-.236-.764.013-1.936.42-7.579 2.767C11.39 7.03 7.44 8.73 1.517 11.31z'/%3E%3C/svg%3E")}.shareon>.twitter{background-color:#1da1f2}.shareon>.twitter:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M23.954 4.569a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.691 8.094 4.066 6.13 1.64 3.161a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.061a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.937 4.937 0 004.604 3.417 9.868 9.868 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.054 0 13.999-7.496 13.999-13.986 0-.209 0-.42-.015-.63a9.936 9.936 0 002.46-2.548l-.047-.02z'/%3E%3C/svg%3E")}.shareon>.viber{background-color:#7360f2}.shareon>.viber:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11.398.002C9.473.028 5.331.344 3.014 2.467 1.294 4.177.693 6.698.623 9.82c-.06 3.11-.13 8.95 5.5 10.541v2.42s-.038.97.602 1.17c.79.25 1.24-.499 1.99-1.299l1.4-1.58c3.85.32 6.8-.419 7.14-.529.78-.25 5.181-.811 5.901-6.652.74-6.031-.36-9.831-2.34-11.551l-.01-.002c-.6-.55-3-2.3-8.37-2.32 0 0-.396-.025-1.038-.016zm.067 1.697c.545-.003.88.02.88.02 4.54.01 6.711 1.38 7.221 1.84 1.67 1.429 2.528 4.856 1.9 9.892-.6 4.88-4.17 5.19-4.83 5.4-.28.09-2.88.73-6.152.52 0 0-2.439 2.941-3.199 3.701-.12.13-.26.17-.35.15-.13-.03-.17-.19-.16-.41l.02-4.019c-4.771-1.32-4.491-6.302-4.441-8.902.06-2.6.55-4.732 2-6.172 1.957-1.77 5.475-2.01 7.11-2.02zm.36 2.6a.299.299 0 00-.3.299.3.3 0 00.3.3 5.631 5.631 0 014.03 1.59c1.09 1.06 1.621 2.48 1.641 4.34a.3.3 0 00.3.3v-.009a.3.3 0 00.3-.3 6.451 6.451 0 00-1.81-4.76c-1.19-1.16-2.692-1.76-4.462-1.76zm-3.954.69a.955.955 0 00-.615.12h-.012c-.41.24-.788.54-1.148.94-.27.32-.421.639-.461.949a1.24 1.24 0 00.05.541l.02.01a13.722 13.722 0 001.2 2.6 15.383 15.383 0 002.32 3.171l.03.04.04.03.03.03.03.03a15.603 15.603 0 003.18 2.33c1.32.72 2.122 1.06 2.602 1.2v.01c.14.04.268.06.398.06a1.84 1.84 0 001.102-.472c.39-.35.7-.738.93-1.148v-.01c.23-.43.15-.841-.18-1.121a13.632 13.632 0 00-2.15-1.54c-.51-.28-1.03-.11-1.24.17l-.45.569c-.23.28-.65.24-.65.24l-.012.01c-3.12-.8-3.95-3.959-3.95-3.959s-.04-.43.25-.65l.56-.45c.27-.22.46-.74.17-1.25a13.522 13.522 0 00-1.54-2.15.843.843 0 00-.504-.3zm4.473.89a.3.3 0 00.002.6 3.78 3.78 0 012.65 1.15 3.5 3.5 0 01.9 2.57.3.3 0 00.3.299l.01.012a.3.3 0 00.3-.301c.03-1.19-.34-2.19-1.07-2.99-.73-.8-1.75-1.25-3.05-1.34a.3.3 0 00-.042 0zm.49 1.619a.305.305 0 00-.018.611c.99.05 1.47.55 1.53 1.58a.3.3 0 00.3.29h.01a.3.3 0 00.29-.32c-.07-1.34-.8-2.091-2.1-2.161a.305.305 0 00-.012 0z'/%3E%3C/svg%3E")}.shareon>.vkontakte{background-color:#4680c2}.shareon>.vkontakte:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M23.058 19.504h-2.616c-.99 0-1.297-.787-3.076-2.59-1.55-1.501-2.236-1.704-2.617-1.704-.534 0-.687.153-.687.89v2.363c0 .636-.202 1.017-1.88 1.017-2.77 0-5.845-1.677-8.004-4.804C.925 10.103.034 6.672.034 5.961c0-.381.153-.737.89-.737H3.54c.66 0 .915.305 1.17 1.016 1.295 3.736 3.456 7.014 4.345 7.014.33 0 .483-.153.483-.99V8.399c-.102-1.78-1.042-1.931-1.042-2.566 0-.306.255-.61.66-.61h4.117c.56 0 .762.304.762.964v5.211c0 .558.255.762.407.762.33 0 .61-.204 1.22-.813 1.88-2.11 3.227-5.362 3.227-5.362.178-.381.483-.737 1.145-.737h2.616c.788 0 .966.405.788.965-.33 1.526-3.532 6.048-3.532 6.048-.28.457-.381.66 0 1.17.28.381 1.194 1.169 1.805 1.88 1.118 1.27 1.98 2.338 2.21 3.076.255.735-.128 1.116-.864 1.116z'/%3E%3C/svg%3E")}.shareon>.whatsapp{background-color:#25d366}.shareon>.whatsapp:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51a12.8 12.8 0 00-.57-.01c-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z'/%3E%3C/svg%3E")} \ No newline at end of file diff --git a/class/2-data-wrangling/libs/shareon/shareon.min.js b/class/2-data-wrangling/libs/shareon/shareon.min.js new file mode 100644 index 0000000..8a1d9db --- /dev/null +++ b/class/2-data-wrangling/libs/shareon/shareon.min.js @@ -0,0 +1,6 @@ +/*! + * shareon v1.4.1 by Nikita Karamov + * https://shareon.js.org + */ + +var shareon=function(){"use strict";var t={facebook:t=>"https://www.facebook.com/sharer/sharer.php?u="+t.url,linkedin:t=>`https://www.linkedin.com/shareArticle?mini=true&url=${t.url}&title=${t.title}`,messenger:t=>`https://www.facebook.com/dialog/send?app_id=3619024578167617&link=${t.url}&redirect_uri=${t.url}`,odnoklassniki:t=>`https://connect.ok.ru/offer?url=${t.url}&title=${t.title}${t.media?"&imageUrl="+t.media:""}`,pinterest:t=>`https://pinterest.com/pin/create/button/?url=${t.url}&description=${t.title}${t.media?"&media="+t.media:""}`,pocket:t=>"https://getpocket.com/edit.php?url="+t.url,reddit:t=>`https://www.reddit.com/submit?title=${t.title}&url=${t.url}`,telegram:t=>`https://telegram.me/share/url?url=${t.url}${t.text?"&text="+t.text:""}`,twitter:t=>`https://twitter.com/intent/tweet?url=${t.url}&text=${t.title}${t.via?"&via="+t.via:""}`,viber:t=>`viber://forward?text=${t.title}%0D%0A${t.url}${t.text?"%0D%0A%0D%0A"+t.text:""}`,vkontakte:t=>`https://vk.com/share.php?url=${t.url}&title=${t.title}${t.media?"&image="+t.media:""}`,whatsapp:t=>`whatsapp://send?text=${t.title}%0D%0A${t.url}${t.text?"%0D%0A%0D%0A"+t.text:""}`};const e=()=>{const e=document.getElementsByClassName("shareon");for(let r=0;r{window.open(o,"_blank","noopener,noreferrer").opener=null});break}}}}}};return window.onload=()=>{e()},e}(); diff --git a/class/2-data-wrangling/libs/tile-view/tile-view.css b/class/2-data-wrangling/libs/tile-view/tile-view.css new file mode 100644 index 0000000..e1870da --- /dev/null +++ b/class/2-data-wrangling/libs/tile-view/tile-view.css @@ -0,0 +1,52 @@ +.remark__tile-view * { + box-sizing: border-box; +} + +.remark__tile-view { + background: lightgray; + position: relative; + width: 100%; + height: 100%; + padding: 3em; + font-size: 18px; + box-sizing: border-box; + overflow: scroll; +} + +.remark__tile-view__header { + text-align: center; +} + +.remark__tile-view__tiles { + display: grid; + /* Set column width in JS */ + /* grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); */ + justify-items: center; +} + +.remark__tile-view__tile { + position: relative; + margin: 0.5em; + padding: 0.5em; +} + +.remark__tile-view__slide-container { + margin: 0 auto; +} + +.remark__tile-view__tile--current { + background: #ffd863; + border: 5px solid #ffd863; + margin: calc(0.5em - 5px); + border-radius: 0; +} + +.remark__tile-view__tile--seen { + opacity: 0.5; +} + +.remark__tile-view__tile:hover { + /* background: #993d70; */ + background: #44bc96; + opacity: 1; +} diff --git a/class/2-data-wrangling/libs/tile-view/tile-view.js b/class/2-data-wrangling/libs/tile-view/tile-view.js new file mode 100644 index 0000000..bb31dc2 --- /dev/null +++ b/class/2-data-wrangling/libs/tile-view/tile-view.js @@ -0,0 +1,177 @@ +/* + * Tile View for remark.js Slides + * + * Garrick Aden-Buie + * + * Inspired and converted to Vanilla JS from + * https://github.com/StephenHesperus/remark-hook/ + * + * Include after remarkjs slides are initialized. + * + */ + +/* global slideshow */ +(function () { + const ready = function (fn) { + /* MIT License Copyright (c) 2016 Nuclei */ + /* https://github.com/nuclei/readyjs */ + const completed = () => { + document.removeEventListener('DOMContentLoaded', completed) + window.removeEventListener('load', completed) + fn() + } + if (document.readyState !== 'loading') { + setTimeout(fn) + } else { + document.addEventListener('DOMContentLoaded', completed) + window.addEventListener('load', completed) + } + } + + ready(function () { + const launchKey = 79 // keycode for O, used to enable tile view + + // Slides container + const remarkSlideShow = document.querySelector('div.remark-slides-area') + + let tileView = document.querySelector('div.remark__tile-view') + if (!tileView) { + tileView = document.createElement('div') + tileView.className = 'remark__tile-view' + } + + const toggleElement = el => { + el.style.display = el.style.display === 'none' ? '' : 'none' + } + + function slideshowResize () { + window.dispatchEvent(new Event('resize')) + } + + const toggleTileView = function () { + toggleElement(tileView) + toggleElement(remarkSlideShow) + + if (tileView.style.display === 'none') { + // tileView is now hidden, go to current slide + slideshow.gotoSlide(tileVars.currentSlideIdx + 1) + + slideshow.resume() + slideshowResize() + } else { + // store current slide index prior to launching tile-view + tileVars.currentSlideIdx = slideshow.getCurrentSlideIndex() + + // set class on seen and current slide and scroll into view + const tiles = tileView.querySelectorAll('.remark__tile-view__tile'); + [...tiles].forEach((tile, idx) => { + tile.classList.toggle( + 'remark__tile-view__tile--seen', + idx < tileVars.currentSlideIdx + ) + tile.classList.toggle( + 'remark__tile-view__tile--current', + idx === tileVars.currentSlideIdx + ) + }) + tiles[tileVars.currentSlideIdx].scrollIntoView({ + behavior: 'smooth', + block: 'center' + }) + + slideshow.pause() + } + } + + const createTileView = ({ minSize = 250, title = document.title } = {}) => { + // Tile view header + const h1 = document.createElement('h1') + h1.className = 'remark__tile-view__header' + h1.innerHTML = title + + tileView.appendChild(h1) + const tiles = document.createElement('div') + tiles.className = 'remark__tile-view__tiles' + tileView.appendChild(tiles) + + // Clone slideshow + const slidesArea = remarkSlideShow.cloneNode(true) + + // Calculate slide scale and tile container size + const slideScaler = slidesArea.querySelector('.remark-slide-scaler') + const slideWidth = parseFloat(slideScaler.style.width.replace('px', '')) + const slideHeight = parseFloat( + slideScaler.style.height.replace('px', '') + ) + const scale = minSize / Math.min(slideWidth, slideHeight) + let tileWidth = Math.round(slideWidth * scale) + let tileHeight = Math.round(slideHeight * scale) + + // convert tileWidth/Height to em relative to base 18px (set in CSS) + tileWidth = tileWidth / 18 + tileHeight = tileHeight / 18 + + tiles.style.gridTemplateColumns = `repeat(auto-fill, minmax(${tileWidth}em, 1fr))` + + const slides = slidesArea.querySelectorAll('.remark-slide-container') + + slides.forEach((slide, slideIndex) => { + let tile = document.createElement('template') + tile.innerHTML = `

+
+
` + tile = tile.content.firstChild + + const tileContainer = tile.querySelector( + '.remark__tile-view__slide-container' + ) + tileContainer.style.width = `${tileWidth}em` + tileContainer.style.height = `${tileHeight}em` + + const thisSlideScaler = slide.querySelector('.remark-slide-scaler') + thisSlideScaler.style.top = '0px' + thisSlideScaler.style.left = '0px' + thisSlideScaler.style.transform = `scale(${scale})` + thisSlideScaler.parentElement.classList.add('remark-visible') + + slide.addEventListener('click', () => { + tileVars.currentSlideIdx = slideIndex + toggleTileView() + }) + + tileContainer.appendChild(slide) + tiles.appendChild(tile) + }) + + document.body.appendChild(tileView) + } + + const tileVars = {} + + document.addEventListener('keydown', ev => { + if (ev.keyCode === launchKey) { + toggleTileView() + } + }) + + const addTileViewHelpText = () => { + const helpTable = document.querySelector( + '.remark-help-content table.light-keys' + ) + if (!helpTable) { + console.error( + 'Could not find remark help table, has remark been initialized?' + ) + return + } + const newRow = document.createElement('tr') + newRow.innerHTML += 'o' + newRow.innerHTML += 'Tile View: Overview of Slides' + helpTable.append(newRow) + } + + createTileView({ minSize: 200 }) + toggleElement(tileView) + addTileViewHelpText() + }) +})() diff --git a/class/2-data-wrangling/libs/xaringanExtra-extra-styles/xaringanExtra-extra-styles.css b/class/2-data-wrangling/libs/xaringanExtra-extra-styles/xaringanExtra-extra-styles.css new file mode 100644 index 0000000..66d21b4 --- /dev/null +++ b/class/2-data-wrangling/libs/xaringanExtra-extra-styles/xaringanExtra-extra-styles.css @@ -0,0 +1,22 @@ +/* Line Hover Indicator */ +.remark-code-line:hover { + font-weight: bold; + opacity: 1 !important; +} + +.remark-code-line:hover:before { + content: "\25B6"; + color: #6d7e8a; + position: absolute; + transform: translateX(-1.2em); + animation: hover 0.66s alternate 8 cubic-bezier(0.445, 0.05, 0.55, 0.95); +} + +@keyframes hover { + 0% { + transform: translateX(-1.2em); + } + 100% { + transform: translateX(-0.8em); + } +} diff --git a/class/2-data-wrangling/libs/xaringanExtra-shareagain/shareagain.css b/class/2-data-wrangling/libs/xaringanExtra-shareagain/shareagain.css new file mode 100644 index 0000000..ba9f03e --- /dev/null +++ b/class/2-data-wrangling/libs/xaringanExtra-shareagain/shareagain.css @@ -0,0 +1,306 @@ +/* Animations by animate.css */ + +@-webkit-keyframes slideOutDown { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +@keyframes slideOutDown { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +@-webkit-keyframes slideInUp { + from { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInUp { + from { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@-webkit-keyframes slideOutRight { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +@keyframes slideOutRight { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +@-webkit-keyframes slideInRight { + from { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInRight { + from { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slideOutDown { + -webkit-animation-name: slideOutDown; + animation-name: slideOutDown; + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; +} + +.slideInUp { + -webkit-animation-name: slideInUp; + animation-name: slideInUp; + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; +} + +.slideInRight { + -webkit-animation-name: slideInRight; + animation-name: slideInRight; + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; +} + +.slideOutRight { + -webkit-animation-name: slideOutRight; + animation-name: slideOutRight; + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; +} + +/* shareagain css rules */ +.shareagain-bar { + --shareagain-background: #00000088; + --shareagain-foreground: #FFFFFF; + --shareagain-twitter: inline-block; + --shareagain-facebook: inline-block; + --shareagain-linkedin: inline-block; + --shareagain-pinterest: inline-block; + --shareagain-pocket: inline-block; + --shareagain-reddit: inline-block; +} + +.shareagain-bar { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 66px; + z-index: 1000; +} + +.shareagain-bar ul { + background: var(--shareagain-background); + color: var(--shareagain-foreground); + display: grid; + grid-template-columns: auto 1fr auto; + grid-gap: 5px; + justify-items: center; + align-items: center; + font-family: sans-serif; + list-style: none; + padding-left: 0; + margin: 0; + height: 100%; + width: 100%; +} + +.shareagain-buttons { + height: 100%; +} + +.shareagain-button { + height: 100%; + width: 66px; + border: none; + padding: 15px; + color: var(--shareagain-foreground); + background: transparent; +} + +.shareagain-button:hover { + background: rgba(0,0,0,.25); +} + +.shareagain-button.disabled { + color: #aaa; + cursor: not-allowed; +} + +.shareagain-button svg { + height: 100%; + width: 100%; +} + +.shareagain-button svg path { + stroke-width: 1px; +} + +.shareagain-title { + font-size: 24px; + text-align: center; + max-height: 100%; + overflow: hidden; +} + +.shareagain-bar .shareon { + font-size: 0 !important; + position: absolute; + top: -45px; + width: 100vw; + right: 0; + text-align: right; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); +} + +.shareagain-bar .shareon > .link { + background-color: #cacaca; +} +.shareagain-bar .shareon > .link::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor' class='clipboard-copy w-6 h-6'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3'%3E%3C/path%3E%3C/svg%3E"); +} + +.shareagain-bar .shareon > .link.success::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor' class='clipboard-check w-6 h-6'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4'%3E%3C/path%3E%3C/svg%3E"); +} + +.shareagain-bar .shareon .twitter { display: var(--shareagain-twitter); } +.shareagain-bar .shareon .facebook { display: var(--shareagain-facebook); } +.shareagain-bar .shareon .linkedin { display: var(--shareagain-linkedin); } +.shareagain-bar .shareon .pinterest { display: var(--shareagain-pinterest); } +.shareagain-bar .shareon .pocket { display: var(--shareagain-pocket); } +.shareagain-bar .shareon .reddit { display: var(--shareagain-reddit); } + +@media screen and (max-width: 600px) { + .shareagain-bar { + font-size: 10px; + height: 50px; + } + .shareagain-buttons .shareagain-button { + width: 50px; + } + .shareagain-title { + font-size: 16px; + } +} +@media screen and (max-width: 400px) { + .shareagain-bar { + height: 30px; + } + .shareagain-bar ul { + grid-template-columns: auto auto; + justify-content: space-between; + } + .shareagain-bar .shareagain-title { + display: none; + } + .shareagain-buttons .shareagain-button { + width: 30px; + padding: 5px; + } +} + +@media screen and (max-width: 400px) { + .shareagain-bar { + height: 30px; + } + .shareagain-bar ul { + grid-template-columns: auto auto; + justify-content: space-between; + } + .shareagain-bar .shareagain-title { + display: none; + } + .shareagain-buttons .shareagain-button { + width: 30px; + padding: 5px; + } + .shareagain-buttons .shareon > * { + width: 15px; + height: 20px; + } + .shareagain-buttons .shareon > ::before { + transform: scale(0.66); + top: 7px; + } + .shareagain-buttons .shareon .link { + width: 23px; + } + .shareagain-buttons .shareon > .link::before { + top: 5px; + left: 6px; + } +} diff --git a/class/2-data-wrangling/libs/xaringanExtra-shareagain/shareagain.js b/class/2-data-wrangling/libs/xaringanExtra-shareagain/shareagain.js new file mode 100644 index 0000000..9abc135 --- /dev/null +++ b/class/2-data-wrangling/libs/xaringanExtra-shareagain/shareagain.js @@ -0,0 +1,260 @@ +/* global slideshow,ClipboardJS */ +(function () { + function inIframe () { + try { + return window.self !== window.top && + !window.self.location.href.match('viewer_pane=1') // in RStudio + } catch (e) { + return true + } + } + + if (!inIframe()) return + + const ready = function (fn) { + /* MIT License Copyright (c) 2016 Nuclei */ + /* https://github.com/nuclei/readyjs */ + const completed = () => { + document.removeEventListener('DOMContentLoaded', completed) + window.removeEventListener('load', completed) + fn() + } + if (document.readyState !== 'loading') { + setTimeout(fn) + } else { + document.addEventListener('DOMContentLoaded', completed) + window.addEventListener('load', completed) + } + } + + ready(addShareAgainBar) + + function addShareAgainBar () { + const icons = { + left: '', + right: '', + fullScreen: '', + share: '' + } + + function getAuthor () { + let author = document.head.querySelector('meta[name="author"]') + + if (author) { + author = author.content + ' — ' + } else { + author = '' + } + return author + } + + function truncate (str, n, useWordBoundary = true) { + if (str.length <= n) { return str } + const subString = str.substr(0, n - 1) + return (useWordBoundary + ? subString.substr(0, subString.lastIndexOf(' ')) + : subString) + '…' + }; + + function getShortTitle () { + return truncate(window.document.title || '', 50) + } + + const navbar = document.createElement('nav') + navbar.classList.add('shareagain-bar') + + navbar.innerHTML += `` + + const slidesContainer = document.querySelector(':not(html).remark-container') + slidesContainer.appendChild(navbar) + + const btnSlidePrev = document.getElementById('shareagain-slide-prev') + const btnSlideNext = document.getElementById('shareagain-slide-next') + + function toggleSlideButtons (slideIndex) { + if (typeof slideIndex === 'undefined') { + slideIndex = slideshow.getCurrentSlideIndex() + } + + // Toggle next slide button + if (slideIndex + 1 === slideshow.getSlideCount()) { + btnSlideNext.classList.add('disabled') + btnSlideNext.setAttribute('disabled', true) + } else { + btnSlideNext.classList.remove('disabled') + btnSlideNext.removeAttribute('disabled') + } + + // Toggle prev slide button + if (slideIndex === 0) { + btnSlidePrev.classList.add('disabled') + btnSlidePrev.setAttribute('disabled', true) + } else { + btnSlidePrev.classList.remove('disabled') + btnSlidePrev.removeAttribute('disabled') + } + } + + setTimeout(toggleSlideButtons, 100) + + // button click events + btnSlidePrev.addEventListener('click', function (ev) { + slideshow.gotoPreviousSlide() + }) + + btnSlideNext.addEventListener('click', function (ev) { + slideshow.gotoNextSlide() + }) + + // button touch events (block remarkjs slide change on touch) + btnSlidePrev.addEventListener('touchend', function (ev) { + slideshow.gotoPreviousSlide() + ev.stopPropagation() + }) + + btnSlideNext.addEventListener('touchend', function (ev) { + slideshow.gotoNextSlide() + ev.stopPropagation() + }); + + // show/hide share buttons + ['click', 'touchend'].map(function (evType) { + document.getElementById('shareagain-share').addEventListener(evType, function (ev) { + toggleShareButtons() + ev.preventDefault() + ev.stopPropagation() + }) + }) + + navbar.addEventListener('touchend', function (ev) { + ev.preventDefault() + ev.stopPropagation() + }) + + // copy slides link to clipboard + const shareClip = new ClipboardJS('.shareon .link') + shareClip.on('success', function (e) { + const el = document.querySelector('.shareon .link') + el.classList.add('success') + setTimeout(() => el.classList.remove('success'), 2500) + }) + + slideshow.on('afterShowSlide', function (slide) { + toggleSlideButtons(slide.getSlideIndex()) + }) + + function toggleAnimated ({ selector, show, inClass, outClass }) { + const el = document.querySelector(selector) + const isShown = el.classList.contains(inClass) + if (typeof show === 'undefined') { + show = !isShown + } + if (show === isShown) { + return + } + if (show) { + el.classList.remove(outClass) + el.classList.add(inClass) + } else { + el.classList.remove(inClass) + el.classList.add(outClass) + } + } + + let isCurrentlyFullScreen = false + document.addEventListener('fullscreenchange', (event) => { + if (document.fullscreenElement) { + stopAutoHide(false) + isCurrentlyFullScreen = true + } else { + isCurrentlyFullScreen = false + startAutoHide() + } + }) + + function toggleNavBar (show) { + // do nothing if currently fullscreen + if (isCurrentlyFullScreen) return + + toggleAnimated({ selector: '.shareagain-bar ul', show, inClass: 'slideInUp', outClass: 'slideOutDown' }) + if (!show) toggleShareButtons(false) + } + + function toggleShareButtons (show) { + toggleAnimated({ selector: '.shareagain-bar .shareon', show, inClass: 'slideInRight', outClass: 'slideOutRight' }) + } + + // auto hide the share bar when focus is in the slides + let mouseMoveTimer = null + function hideNavDelayed (ev) { + if (mouseMoveTimer) { + clearTimeout(mouseMoveTimer) + } + toggleNavBar(true) + mouseMoveTimer = setTimeout(function () { toggleNavBar(false) }, 2000) + }; + + // toggle toggle full screen + ['click', 'touchend'].map(function (evType) { + document.getElementById('shareagain-fullscreen').addEventListener(evType, function (ev) { + slideshow.toggleFullscreen() + ev.stopPropagation() + }) + }) + + function startAutoHide () { + hideNavDelayed() + slidesContainer.addEventListener('mousemove', hideNavDelayed) + } + + function stopAutoHide (showAfter = false) { + slidesContainer.removeEventListener('mousemove', hideNavDelayed) + clearTimeout(mouseMoveTimer) + toggleNavBar(showAfter) + } + + // auto hide turns on when the mouse comes into the slides area + slidesContainer.addEventListener('mouseenter', function () { + startAutoHide() + }) + + // turn off auto hide when the mouse leaves the slides area + slidesContainer.addEventListener('mouseleave', function () { + stopAutoHide(true) + }); + + // turn off auto hide if the mouse or focus is in the share bar + ['focusin', 'mouseenter'].map(function (evType) { + navbar.addEventListener(evType, function () { + stopAutoHide(true) + }) + }); + + // and turn auto hide back on whne the mouse leaves the share bar + // (if mouse exits out of the slides area, the slidesContainer should fire later) + ['focusout', 'mouseleave'].map(function (evType) { + navbar.addEventListener(evType, function () { + startAutoHide() + }) + }) + } +})() diff --git a/class/2-data-wrangling/practice-solutions.R b/class/2-data-wrangling/practice-solutions.R new file mode 100644 index 0000000..2d97d90 --- /dev/null +++ b/class/2-data-wrangling/practice-solutions.R @@ -0,0 +1,172 @@ + # install.packages(c("tidyverse", "here")) +library(tidyverse) +library(here) + +# The setting below is optional, it lets you see all the columns in a data frame +options(dplyr.width = Inf) + + + +# DATA FRAMES -------------------------------------------------------------- + +beatles <- tibble( + firstName = c("John", "Paul", "Ringo", "George"), + lastName = c("Lennon", "McCartney", "Starr", "Harrison"), + instrument = c("guitar", "bass", "drums", "guitar"), + yearOfBirth = c(1940, 1942, 1940, 1943), + deceased = c(TRUE, FALSE, FALSE, TRUE) +) + +beatles + +# Extract rows and columns using df[row, col] +beatles[1,] # First row +beatles[,1] # First column + +# Extract whole columns using df$name +beatles$firstName +beatles$lastName + +# Get the dimensions +nrow(beatles) # number of rows +ncol(beatles) # number of columns +dim(beatles) # numbers of rows, columns + +# View dataset in new tab +view(beatles) # This is "read-only" so you can't corrupt the data :) + +# View data types of each column +glimpse(beatles) + + + +# DATA FROM PACKAGES ---------------------------------------------------------- + +# See which data frames are available in a package: +data(package = "ggplot2") # note: ggplot2 is part of the "tidyverse" package + +# Find out more about a package data set +?msleep + +# Look at the variables and their types +glimpse(msleep) + +# View the **first** or **last** 6 rows with head() or tail() +head(msleep) +tail(msleep) + +# View first 10 rows +head(msleep, 10) + +# View dataset in new tab +view(msleep) + +# Get variable names +names(msleep) +colnames(msleep) + + + +# DATA FROM CSV FILE ---------------------------------------------------------- + +# Build path to data using here() +path_to_data <- here('data', 'data.csv') +path_to_data + +# Read in data using read_csv() +data <- read_csv(path_to_data) + +# How many rows and columns are in the data frame? +nrow(data) +ncol(data) +dim(data) + +# What type of data is each column? +# (Just look, don't need to type out the answer) +glimpse(data) + +# Preview the different columns - what do you think this data is about? +# What might one row represent? + +# ANSWER: +# These are observations of bird impacts with aircraft. Each row +# is one impact observation + +# How many unique airports are in the data frame? +# HINT: Use unique() +unique(data$airport) # View all airports +length(unique(data$airport)) # Count how many there are + +# What is the earliest and latest observation in the data frame? +# HINT: Use min() and max() +min(data$incident_date) +max(data$incident_date) + +# What is the lowest and highest cost of any one repair in the data frame? +# HINT: Use min() and max() with na.rm = TRUE +min(data$cost_repairs_infl_adj, na.rm = TRUE) +max(data$cost_repairs_infl_adj, na.rm = TRUE) + + + +# filter() & select() --------------------------------------------------------- + +# Read in the data.csv file in the data folder: +path_to_data <- here('data', 'data.csv') +data <- read_csv(path_to_data) + +# Create a new data frame, dc, that contains only the rows from DC airports. +dc <- data %>% + filter(state == 'DC') + +# Create a new data frame, dc_dawn, that contains only the rows from DC +# airports that occurred at dawn. +dc_dawn <- data %>% + filter(state == 'DC') %>% + filter(time_of_day == 'Dawn') + +dc_dawn <- data %>% + filter(state == 'DC', time_of_day == 'Dawn') + +dc_dawn <- data %>% + filter(state == 'DC' & time_of_day == 'Dawn') + +# Create a new data frame, dc_dawn_birds, that contains only the rows from DC +# airports that occurred at dawn and only the variables (columns) about the +# species of bird. +dc_dawn_birds <- data %>% + filter(state == 'DC', time_of_day == 'Dawn') %>% + select(contains('species')) + +# How many unique species of birds have been involved in accidents at DC +# airports? +unique(dc_dawn_birds$species) +length(unique(dc_dawn_birds$species)) + + + +# mutate() & arrange() -------------------------------------------------------- + +# Read in the data.csv file in the data folder: +path_to_data <- here('data', 'data.csv') +data <- read_csv(path_to_data) + +# Create the height_miles variable (the height variable converted to miles) +# HINT: There are 5,280 feet in a mile +data <- data %>% + mutate(height_miles = height / 5280) + +# Create the cost_mil variable +# TRUE if the repair cost >= $1 million, otherwise FALSE +data <- data %>% + mutate(cost_mil = ifelse(cost_repairs_infl_adj > 10^6, TRUE, FALSE)) + +data <- data %>% + mutate(cost_mil = cost_repairs_infl_adj > 10^6) # This also works + +# Remove rows that have NA for cost_repairs_infl_adj and re-arrange the +# resulting data frame based on the highest height and most expensive cost +# HINT: The function is.na() returns TRUE if a value is NA +data <- data %>% + filter(!is.na(cost_repairs_infl_adj)) %>% + arrange(desc(height), desc(cost_repairs_infl_adj)) diff --git a/class/2-data-wrangling/practice.R b/class/2-data-wrangling/practice.R new file mode 100644 index 0000000..ba065aa --- /dev/null +++ b/class/2-data-wrangling/practice.R @@ -0,0 +1,125 @@ + # install.packages(c("tidyverse", "here")) +library(tidyverse) +library(here) + +# The setting below is optional, it lets you see all the columns in a data frame +options(dplyr.width = Inf) + + + +# DATA FRAMES -------------------------------------------------------------- + +beatles <- tibble( + firstName = c("John", "Paul", "Ringo", "George"), + lastName = c("Lennon", "McCartney", "Starr", "Harrison"), + instrument = c("guitar", "bass", "drums", "guitar"), + yearOfBirth = c(1940, 1942, 1940, 1943), + deceased = c(TRUE, FALSE, FALSE, TRUE) +) + +beatles + +# Extract rows and columns using df[row, col] +beatles[1,] # First row +beatles[,1] # First column + +# Extract whole columns using df$name +beatles$firstName +beatles$lastName + +# Get the dimensions +nrow(beatles) # number of rows +ncol(beatles) # number of columns +dim(beatles) # numbers of rows, columns + +# View dataset in new tab +view(beatles) # This is "read-only" so you can't corrupt the data :) + +# View data types of each column +glimpse(beatles) + + + +# DATA FROM PACKAGES ---------------------------------------------------------- + +# See which data frames are available in a package: +data(package = "ggplot2") # note: ggplot2 is part of the "tidyverse" package + +# Find out more about a package data set +?msleep + +# Look at the variables and their types +glimpse(msleep) + +# View the **first** or **last** 6 rows with head() or tail() +head(msleep) +tail(msleep) + +# View first 10 rows +head(msleep, 10) + +# View dataset in new tab +view(msleep) + +# Get variable names +names(msleep) +colnames(msleep) + + + +# DATA FROM CSV FILE ---------------------------------------------------------- + +# Build path to data using here() + +# Read in data using read_csv() + +# How many rows and columns are in the data frame? + +# What type of data is each column? +# (Just look, don't need to type out the answer) + +# Preview the different columns - what do you think this data is about? +# What might one row represent? + +# How many unique airports are in the data frame? +# HINT: Use unique() + +# What is the earliest and latest observation in the data frame? +# HINT: Use min() and max() + +# What is the lowest and highest cost of any one repair in the data frame? +# HINT: Use min() and max() with na.rm = TRUE + + + +# filter() & select() --------------------------------------------------------- + +# Read in the data.csv file in the data folder: + +# Create a new data frame, dc, that contains only the rows from DC airports. + +# Create a new data frame, dc_dawn, that contains only the rows from DC +# airports that occurred at dawn. + +# Create a new data frame, dc_dawn_birds, that contains only the rows from DC +# airports that occurred at dawn and only the variables (columns) about the +# species of bird. + +# How many unique species of birds have been involved in accidents at DC +# airports? + + + +# mutate() & arrange() -------------------------------------------------------- + +# Read in the data.csv file in the data folder: + +# Create the height_miles variable (the height variable converted to miles) +# HINT: There are 5,280 feet in a mile + +# Create the cost_mil variable +# TRUE if the repair cost >= $1 million, otherwise FALSE + +# Remove rows that have NA for cost_repairs_infl_adj and re-arrange the +# resulting data frame based on the highest height and most expensive cost +# HINT: The function is.na() returns TRUE if a value is NA diff --git a/class/2-data-wrangling/topics/0.Rmd b/class/2-data-wrangling/topics/0.Rmd new file mode 100644 index 0000000..6629a84 --- /dev/null +++ b/class/2-data-wrangling/topics/0.Rmd @@ -0,0 +1,11 @@ + +class: inverse, middle + +# Week `r rmarkdown::metadata$week`: .fancy[`r rmarkdown::metadata$title`] + +### 1. Working with data frames +### 2. Data wrangling with the _tidyverse_ + +### BREAK + +### 3. Project proposals diff --git a/class/2-data-wrangling/topics/1.Rmd b/class/2-data-wrangling/topics/1.Rmd new file mode 100644 index 0000000..634114f --- /dev/null +++ b/class/2-data-wrangling/topics/1.Rmd @@ -0,0 +1,11 @@ + +class: inverse, middle + +# Week `r rmarkdown::metadata$week`: .fancy[`r rmarkdown::metadata$title`] + +### 1. .orange[Working with data frames] +### 2. Data wrangling with the _tidyverse_ + +### BREAK + +### 3. Project proposals diff --git a/class/2-data-wrangling/topics/2.Rmd b/class/2-data-wrangling/topics/2.Rmd new file mode 100644 index 0000000..74a6093 --- /dev/null +++ b/class/2-data-wrangling/topics/2.Rmd @@ -0,0 +1,11 @@ + +class: inverse, middle + +# Week `r rmarkdown::metadata$week`: .fancy[`r rmarkdown::metadata$title`] + +### 1. Working with data frames +### 2. .orange[Data wrangling with the _tidyverse_] + +### BREAK + +### 3. Project proposals diff --git a/class/2-data-wrangling/topics/3.Rmd b/class/2-data-wrangling/topics/3.Rmd new file mode 100644 index 0000000..f0a2941 --- /dev/null +++ b/class/2-data-wrangling/topics/3.Rmd @@ -0,0 +1,11 @@ + +class: inverse, middle + +# Week `r rmarkdown::metadata$week`: .fancy[`r rmarkdown::metadata$title`] + +### 1. Working with data frames +### 2. Data wrangling with the _tidyverse_ + +### BREAK + +### 3. .orange[Project proposals] diff --git a/class/3-quarto-plotting/3-quarto-plotting.Rproj b/class/3-quarto-plotting/3-quarto-plotting.Rproj new file mode 100644 index 0000000..a91d965 --- /dev/null +++ b/class/3-quarto-plotting/3-quarto-plotting.Rproj @@ -0,0 +1,15 @@ +Version: 1.0 + +RestoreWorkspace: Default +SaveWorkspace: Default +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 4 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX + +BuildType: Website diff --git a/class/3-quarto-plotting/bears.qmd b/class/3-quarto-plotting/bears.qmd new file mode 100644 index 0000000..4b4e278 --- /dev/null +++ b/class/3-quarto-plotting/bears.qmd @@ -0,0 +1,27 @@ +--- +title: "Bears Analysis" +format: + html: + toc: true + theme: flatly + self-contained: true +--- + + +### Which months have the highest frequency of bear killings? + +```{r} +# Write code here +``` + +### Who has been killed more often by bears: hunters or hikers? + +```{r} +# Write code here +``` + +### How do the the number of bear attacks on men vs women compare? + +```{r} +# Write code here +``` \ No newline at end of file diff --git a/class/3-quarto-plotting/bears_solutions.qmd b/class/3-quarto-plotting/bears_solutions.qmd new file mode 100644 index 0000000..d1a119b --- /dev/null +++ b/class/3-quarto-plotting/bears_solutions.qmd @@ -0,0 +1,58 @@ +--- +title: "Bears Analysis" +format: + html: + toc: true + theme: flatly + self-contained: true +--- + +```{r} +#| label: setup +#| include: false + +knitr::opts_chunk$set( + warning = FALSE, + message = FALSE, + fig.path = "figs/", + fig.width = 7.252, + fig.height = 4, + comment = "#>", + fig.retina = 3 +) + +# Load libraries +library(tidyverse) +library(here) + +# Read in data +bears <- read_csv(here::here('data', 'bear_killings.csv')) +``` + +### Which months have the highest frequency of bear killings? + +```{r} +bears %>% + count(month) %>% + arrange(desc(n)) +``` + +Most attacks occur during the **summer**, with August having the highest number of attacks. + +### Who has been killed more often by bears: hunters or hikers? + +```{r} +bears %>% + count(hunter, hiker) +``` + +Although *hunters* are killed more often than *hikers*, the vast majority of the data set does not record the hunter / hiker status of the victims. + +### How do the the number of bear attacks on men vs women compare? + +```{r} +bears %>% + count(gender) +``` + +Far more attacks have involved men than women. diff --git a/class/3-quarto-plotting/css/lexis-fonts.css b/class/3-quarto-plotting/css/lexis-fonts.css new file mode 100644 index 0000000..4aa0985 --- /dev/null +++ b/class/3-quarto-plotting/css/lexis-fonts.css @@ -0,0 +1,228 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Fira+Sans+Condensed:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Lobster+Two:ital,wght@0,400;0,700;1,400;1,700&display=swap'); + +:root { + --font-body: inter,-apple-system,BlinkMacSystemFont,roboto,segoe ui,Helvetica,Arial,sans-serif; + --font-header: "Fira Sans Condensed",Tahoma,"Helvetica Neue",Helvetica,Arial,sans-serif; + --font-mono: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono",monospace; + --font-fancy: 'Lobster Two',cursive; +} + +body { + font-family: var(--font-body); + font-weight: 400; + color: #000; +} + +strong { + font-family: var(--font-body); + font-weight: 900; + color: #000; +} + +.inverse strong { + font-family: var(--font-body); + font-weight: 900; + color: #fff; +} + +h1, h2, h3, h4, h5, h6 { + font-family: var(--font-header); + font-weight: 400; + line-height: 1.1; +} + +h1, h2, h3, { + margin-top: 5px; + margin-bottom: 5px; +} + +blockquote { + border-left: solid 5px lightgray; + padding-left: 1em; + border: 3px solid var(--color-blue); + border-radius: 10px; /* Rounded edges */ + background-color: var(--color-lightblue); + font-family: var(--font-header); + box-shadow: 4px 4px 8px 0 rgba(0,0,0,.2); +} + +.fira { + font-family: var(--font-header); +} + +.fancy{ + font-family: var(--font-fancy); + font-weight: 400; + font-style: italic; +} + +.remark-slide-content { + font-size: 20pt; +} + +.remark-slide-content h1 { + font-size: 42pt; +} + +.remark-slide-content h2 { + font-size: 34pt; +} + +.remark-slide-content h3 { + font-size: 27pt; +} + +.remark-slide-content h4 { + font-size: 24pt; + font-weight: bold; +} + +.slimtitle { + font-family: var(--font-header); + font-weight: 500; + font-size: 55px; + margin-block-start: 0em; + margin-block-end: 0.1em; +} + +.remark-code, .remark-inline-code { + font-family: var(--font-mono); +} + +.remark-code { + font-size: 20px; +} + +.white { + color: var(--color-white); +} +.red { + color: var(--color-red); +} +.orange { + color: var(--color-orange); +} +.yellow { + color: var(--color-yellow); +} +.green { + color: var(--color-green); +} +.darkgreen { + color: var(--color-darkgreen); +} +.blue { + color: var(--color-blue); +} +.darkblue { + color: var(--color-darkblue); +} +.purple { + color: var(--color-purple); +} +.black { + color: var(--color-black); +} +.gray { + color: var(--color-gray); +} + +/* Additional font sizes */ +.small { + font-size: 70%; +} +.large { + font-size: 160%; +} +.code10 .remark-code { + font-size: 10%; +} +.code20 .remark-code { + font-size: 20%; +} +.code30 .remark-code { + font-size: 30%; +} +.code40 .remark-code { + font-size: 40%; +} +.code50 .remark-code { + font-size: 50%; +} +.code60 .remark-code { + font-size: 60%; +} +.code70 .remark-code { + font-size: 70%; +} +.code80 .remark-code { + font-size: 80%; +} +.code90 .remark-code { + font-size: 90%; +} +.code100 .remark-code { + font-size: 100%; +} +.font10 { + font-size: 10%; +} +.font20 { + font-size: 20%; +} +.font30 { + font-size: 30%; +} +.font40 { + font-size: 40%; +} +.font50 { + font-size: 50%; +} +.font60 { + font-size: 60%; +} +.font70 { + font-size: 70%; +} +.font80 { + font-size: 80%; +} +.font90 { + font-size: 90%; +} +.font100 { + font-size: 100%; +} +.font110 { + font-size: 110%; +} +.font120 { + font-size: 120%; +} +.font130 { + font-size: 130%; +} +.font140 { + font-size: 140%; +} +.font150 { + font-size: 150%; +} +.font160 { + font-size: 160%; +} +.font170 { + font-size: 170%; +} +.font180 { + font-size: 180%; +} +.font190 { + font-size: 190%; +} +.font200 { + font-size: 200%; +} diff --git a/class/3-quarto-plotting/css/lexis.css b/class/3-quarto-plotting/css/lexis.css new file mode 100644 index 0000000..8708323 --- /dev/null +++ b/class/3-quarto-plotting/css/lexis.css @@ -0,0 +1,409 @@ +/* +Adapted from and inspired by: +- Allison Hill's css in this workshop: +https://github.com/rstudio-education/arm-workshop-rsc2019 +- Silvia Canelón's css in this workshop: +https://silvia.rbind.io/talk/2020-11-03-xaringan-basics-and-beyond/ +*/ + +/*--- COLOR VARIABLES ---*/ + +:root { + --color-white: #FFFFFF; + --color-red: #E74A2F; + --color-orange: #fa7b3c; + --color-yellow: #EFB73E; + --color-gold: #e5bf00; + --color-green: #38B44A; + --color-darkgreen: #2c8475; + --color-blue: #007bff; + --color-lightblue: #cce5ff; + --color-dodgerblue: #1E90FF; + --color-darkblue: #003087; + --color-purple: #772953; + --color-black: #121212; + --color-graylight: #EAE9EA; + --color-gray: #696969; + --color-tan: #d9d9d9; +} + +/*--- LINKS/ANCHORS ---*/ + +a { + color: var(--color-blue); + border-bottom: none; +} + +a:visited { + color: var(--color-purple); + border-bottom: none; +} + +a:hover, a:focus { + color: var(--color-purple); + background: rgba(97, 172, 240, .5); + border-bottom: none; +} + +a > code { + color: var(--color-blue); + text-decoration: none; +} + +/*--- SLIDE PROPERTIES ---*/ + +.inverse a, .inverse a:visited { + color: var(--color-dodgerblue ); + text-decoration: underline; +} + +.inverse a:hover, a:focus { + color: var(--color-purple); +} + +/* Background glow color around main slide container */ +.remark-slide-scaler { + -moz-box-shadow: 0 0 0px #000; + -webkit-box-shadow: 0 0 0px #000; +} + +/* Background color of container behind slide */ +.remark-slide-container { + background-color: var(--color-gray); +} + +/* Background color of slide */ +.remark-slide-content { + background-color: var(--color-graylight); + padding-top: 20px; + padding-left: 60px; + padding-right: 60px; + padding-bottom: 20px; +} + +.title-slide { + padding: 0; +} + +/*--- CODE ---*/ + +/* Background color for highlighted code */ +.remark-code-line-highlighted { + background-color: rgb(206, 233, 255); /*light blue*/ +} + +/* Color of inline code, i.e. `code` */ +.remark-inline-code { + color: var(--color-darkgreen); + background-color: transparent; + border-radius: 5px; /* Rounded edges */ +} + +.inverse .remark-inline-code { + color: var(--color-orange); + background-color: transparent; +} + +/* Code chunk colors */ +.hljs-github .hljs { + background: #fff; + border-radius: 5px; /* Rounded edges */ + border: 1px solid #000; +} + +/* Code chunk output */ +.remark-code { + display: block; + overflow-x: auto; + padding: .5em; + color: #fff; + background: var(--color-gray); +} + +.inverse { + background-color: var(--color-black); + color: var(--color-white); + text-shadow: 0 0 0; +} + +/* https://stackoverflow.com/questions/50919104/horizontally-scrollable-output-on-xaringan-slides */ + +pre { + background-color: #000; + border-radius: 5px; /* Rounded edges */ + max-width: 100%; + overflow-x: auto; + margin: 0.5em 0px; + white-space: pre-wrap; /* Wraps R output, https://github.com/yihui/xaringan/issues/225 */ +} + +/* FOOTER */ + +.remark-slide-number { + bottom: 18px; + opacity: 0.5; + position: absolute; + right: 20px; +} + +.footnote { + position: absolute; + bottom: 2em; + padding-right: 4em; + font-size: 70%; + line-height: 1em; +} + +div.footer-large { + position: absolute; + bottom: 150px; + right: 50px; + height: 100px; + width: 100%; + margin-bottom: 0px; +} + +div.footer-small { + background-color: var(--color-black); + position: absolute; + bottom: 0px; + left: 0px; + height: 20px; + width: 100%; +} + +div.footer-small span { + font-size: 10pt; + color: var(--color-white); + position: absolute; + left: 15px; + bottom: 2px; +} + +div.footer-link { + background-color: var(--color-black); + position: absolute; + bottom: 0px; + left: 0px; + height: 30px; + width: 100%; +} + +a.footer-link { + position: absolute; + bottom: 4px; + right: 0px; + height: 30px; + width: 300px; + color: var(--color-blue); + border-bottom: none; + font-size: 0.7em; + opacity: 0.6; +} + +/* IMAGES */ + +.polaroid img { + display: block; + border: 10px solid #fff; + border-bottom: 45px solid #fff; + -webkit-box-shadow: 3px 3px 3px #111; + -moz-box-shadow: 3px 3px 3px #111; + box-shadow: 3px 3px 3px #111; +} + +img { + display: block; + border: 0px solid #000; +} + +.inverse img { + display: block; + border: 0px solid #000; +} + +.noborder img { + display: block; + border: 0px solid #000; +} + +.border img { + display: block; + border: 2px solid #000; +} + +.borderthick img { + display: block; + border: 7px solid #000; +} + +.whiteborder img { + display: block; + border: 2px solid #fff; +} + +.whiteborderthick img { + display: block; + border: 7px solid #fff; +} + +.circle img { + border-radius:50%; +} + +.thumbnail img { + width: 15%; + display: block; + margin-left: auto; + margin-right: auto; +} + +/*--- COLUMN LAYOUTS ---*/ + +.cols3 { + width: 32%; + float: left; + text-align: left; + margin-left: 5px; + margin-right: 5px; +} +.leftcol { + float: left; + width: 49%; +} +.rightcol { + float: right; + width: 49%; +} +.leftcol55 { + float: left; + width: 54%; +} +.rightcol45 { + float: right; + width: 44%; +} +.leftcol45 { + float: left; + width: 44%; +} +.rightcol55 { + float: right; + width: 54%; +} +.leftcol60 { + float: left; + width: 59%; +} +.rightcol40 { + float: right; + width: 39%; +} +.leftcol40 { + float: left; + width: 39%; +} +.rightcol60 { + float: right; + width: 59%; +} +.leftcol65 { + float: left; + width: 64%; +} +.rightcol35 { + float: right; + width: 34%; +} +.leftcol35 { + float: left; + width: 34%; +} +.rightcol65 { + float: right; + width: 64%; +} +.leftcol70 { + float: left; + width: 69%; +} +.rightcol30 { + float: right; + width: 29%; +} +.leftcol30 { + float: left; + width: 29%; +} +.rightcol70 { + float: right; + width: 69%; +} +.leftcol75 { + float: left; + width: 74%; +} +.rightcol25 { + float: right; + width: 25%; +} +.leftcol25 { + float: left; + width: 25%; +} +.rightcol75 { + float: right; + width: 74%; +} +.leftcol80 { + float: left; + width: 79%; +} +.rightcol20 { + float: right; + width: 19%; +} +.leftcol20 { + float: left; + width: 19%; +} +.rightcol80 { + float: right; + width: 79%; +} + +/* Table Row Highlighting */ + +.remark-slide table { + border-collapse: collapse; +} + +.remark-slide table thead th { + border-bottom: 1px solid #666; +} + +.remark-slide .inverse table, .remark-slide .inverse table thead th { + border-top: 3px solid #666; + border-bottom: 3px solid #666; +} + +.remark-slide thead, .remark-slide tfoot, .remark-slide tr:nth-child(even) { + background: var(--color-tan); +} + +.remark-slide .inverse thead, .remark-slide .inverse tfoot, .remark-slide .inverse tr:nth-child(even) { + background: transparent; +} + +.table-blank tr:nth-child(even) { + background: transparent; +} + +/* turns off slide numbers for title page: https://github.com/gnab/remark/issues/298 */ +.title-slide .remark-slide-number { + display: none; +} + +.no-slide-number .remark-slide-number { + display: none; +} diff --git a/class/3-quarto-plotting/data/bear_killings.csv b/class/3-quarto-plotting/data/bear_killings.csv new file mode 100644 index 0000000..d06a977 --- /dev/null +++ b/class/3-quarto-plotting/data/bear_killings.csv @@ -0,0 +1,167 @@ +name,age,gender,date,month,year,wildOrCaptive,location,description,bearType,hunter,grizzly,hiker,onlyOneKilled +Mary Porterfield,3,female,19/05/1901,5,1901,Wild,"Job, West Virginia","The children were gathering flowers near their home when they were attacked. A member of a search party found the remains of the children, and shot and killed the bear.[238]",Black,0,0,0,0 +Wilie Porterfield,5,male,19/05/1901,5,1901,Wild,"Job, West Virginia","The children were gathering flowers near their home when they were attacked. A member of a search party found the remains of the children, and shot and killed the bear.[238]",Black,0,0,0,0 +Henry Porterfield,7,male,19/05/1901,5,1901,Wild,"Job, West Virginia","The children were gathering flowers near their home when they were attacked. A member of a search party found the remains of the children, and shot and killed the bear.[238]",Black,0,0,0,0 +John Dicht,18,male,24/11/1906,11,1906,Wild,"Elk County, Pennsylvania","Thinking the bear was dead, Dicht began skinning it. The bear immediately awoke and tore off one of Dicht's arms, and then killed him.[237]",Black,0,0,0,1 +Baby Laird,1,NA,05/10/1908,10,1908,Captive,"Tucson, Arizona","After a bear escaped from a cage at Elysian Grove Pleasure Park, Buss Laird ran with her infant child in a go-cart. The bear grabbed and killed the baby.[236]",Black,0,0,0,1 +Frank Welch,61,male,08/09/1916,9,1916,Wild,"Yellowstone National Park, Wyoming",Welch was killed at a camp near Sylvan Pass while carrying a load of hay and oats.[235] Men from the camp killed the bear with a dynamite trap.[40],Brown,0,0,0,1 +"Joseph B. ""Frenchy"" Duret",60,male,12/06/1922,6,1922,Wild,"Absaroka-Beartooth Wilderness, Montana",Duret was attacked and partially devoured by a huge grizzly. Duret crawled 1.5-mile (2.4 km) back towards his ranch and died in Frenchy Meadow on Slough Creek.[234],Brown,0,1,0,1 +Olga Gregorchuk,9,female,29/08/1929,8,1929,Wild,"near Lac Du Bonnet, Manitoba","Gregorchuk was minding her four-year-old brother, Bill Gregorchuk, while their parents were out working the farm. A large (420 pound) black bear chased Olga and her brother into their family's farm house where it knocked the door in, attacked Olga, dragged her out and ate her. By the time her body was found, it had been consumed with the exception of her head. A small gravestone in Red Deer Cemetery, Manitoba, describes the death.[230]",Black,0,0,0,1 +Percy Goodair,52,male,12/09/1929,9,1929,Wild,"Jasper National Park, Alberta","Goodair, a Parks Canada warden, was killed by a bear while patrolling the Tonquin Valley.[232][233]",Brown,0,0,0,1 +Jack Thayer,NA,male,16/10/1929,10,1929,Wild,"Admiralty Island, Alaska","Thayer, a U.S. Forest Service employee, and Fred Herring, an assistant, encountered a brown bear at close range while conducting a timber survey on southeast Admiralty Island. Thayer shot the bear while Herring retreated to a tree, but the wounded bear mauled Thayer, who died later that evening.[231]",Brown,0,0,0,1 +Emerson Joyce,60,male,02/06/1930,6,1930,Captive,"Watertown, New York","A female black bear who recently had her cubs taken away killed her feeder, Joyce. This occurred at the John C. Thompson Park Zoo.[224]",Black,0,0,0,1 +Thomas Earl,56,male,08/07/1932,7,1932,Captive,"Cleveland, Ohio","Earl, a zookeeper at the Cleveland Brookside Zoo, was mauled by a brown bear when feeding it in its pen. After a vicious struggle, police shot the bear. Earl was also mistakenly shot, but it was determined that he was already dead.[228] Earlier in the day, Earl had been fired from his job.[229]",Brown,0,0,0,1 +John Macdonald,70,male,01/10/1932,10,1932,Wild,"near Dawson, Yukon","Macdonald's mutilated body was initially found in the wilderness 20 miles (32 km) north of Dawson. Macdonald's corpse was moved to a cabin, and before the police arrived, the bear broke into the cabin and scattered the remains.[226] Macdonald was a woodcutter who lived alone in a shack on the Yukon River.[227]",Brown,0,0,0,1 +Peter Matthew Ryan,5,male,09/10/1932,10,1932,Captive,"Albion, New York",Ryan was attacked after trying to get a close look at a pet bear. The bear was tied to a fence at Mount Albion Cemetery after a truck transporting it broke down.[223],Black,0,0,0,1 +Grant Taylor,11,male,02/10/1933,10,1933,Captive,"Brookhaven, New York","On his walk home from school, Taylor stopped to feed an apple to a bear tethered in front of an inn. The bear mauled Taylor and crushed him against a wire cage. Motorists stopped and used sticks and stones to try to separate the bear from Taylor. Eventually, a man operating a nearby roadside stand came and shot and killed the bear. An examination revealed that the bear hadn't eaten in two days. The Inn had two bears that were trapped five years previously in the Adirondacks, and were frequently fed by passers-by. Both bears were killed.[221][222]",Black,0,0,0,1 +Charles Wyman,76,male,18/07/1934,7,1934,Captive,"Denver, Colorado","Wyman, a zookeeper, was attacked by two grizzly bears at the Denver Zoo after spraying them with a water hose. It was speculated that the bears were in a foul mood due to warm weather. The bears were shot.[225]",Brown,0,1,0,1 +Clarence Staley,54,male,11/11/1934,11,1934,Captive,"Mankato, Minnesota",Staley was mauled by a bear that he had raised from a cub. The caged bear attacked Staley when he tried to retrieve a purse that had been dropped inside the bear's cage.[219],Black,0,0,0,1 +"William Thomas ""Bill"" Brown Jr",64,male,11/11/1934,11,1934,Captive,"Pecos County, Texas",Brown was killed while trying to recapture a bear from his roadside zoo. A posse shot and killed the bear.[220],Black,0,0,0,1 +George Langley,55,male,15/10/1936,10,1936,Captive,"Ellsworth, Maine","Langley owned a gas station where he kept the bear. After entering the bear's cage to feed it, Langley and his helper were attacked. The bear was shot and killed.[218]",Black,0,0,0,0 +James Virtue,68,male,15/10/1936,10,1936,Captive,"Ellsworth, Maine","Langley owned a gas station where he kept the bear. After entering the bear's cage to feed it, Langley and his helper were attacked. The bear was shot and killed.[218]",Black,0,0,0,0 +Thomas Miller,28,male,14/06/1941,6,1941,Captive,"Detroit, Michigan","Miller, a carnival employee, was struck on his head by a bear from Canada. The attack occurred when Miller took the bear out of its cage to perform tricks for his wife.[217]",Brown,0,0,0,1 +Martha Hansen,45,female,23/08/1942,8,1942,Wild,"Yellowstone National Park, Wyoming","Hansen left her cabin to go to the restroom. As she rounded a corner, she surprised a bear and was mauled. Hansen was taken to the hospital and died four days later due to injuries sustained during the attack.[214][215]",Brown,0,0,0,1 +Richard Havemann,68,male,11/12/1942,12,1942,Captive,"San Diego, California","Havemann, an animal trainer, was attacked by a Himalayan brown bear at the San Diego Zoo.[213]",Brown,0,0,0,1 +Carl Herrick,37,male,23/11/1943,11,1943,Wild,"West Townshend, Vermont","Herrick was hunting in West Townshend, Vermont, and his body was found with a blackened face and scratches. His rifle and bear tracks were nearby. A theory is that Herrick shot the bear and thought it was dead, and was squeezed to death when he approached.[210]",Black,1,0,0,1 +Anton Rauch,59,male,02/08/1945,8,1945,Captive,"Chicago, Illinois","Rauch, a worker at the Lincoln Park Zoo, was attacked while cleaning the bear's cage.[209]",Black,0,0,0,1 +Richard Strand,8,male,10/09/1945,9,1945,Captive,"Seattle, Washington",Strand was attacked while playing with a pet bear. The bear also bit a neighbor who attempted to rescue Strand. The bear was taken to Woodland Park Zoo.[211][212],Brown,0,0,0,1 +Carol Ann Pomeranky,3,female,07/07/1948,7,1948,Wild,"Marquette National Forest, Michigan",Pomeranky was taken by a bear outside of her home on the Marquette National Forest (now the Hiawatha National Forest) in Michigan. She was dragged 100 yards (91 m). The bear was tracked and killed.[207][208],Black,0,0,0,1 +Robert Huckins,18,male,19/09/1952,9,1952,Captive,"Crawford Notch State Park, New Hampshire","After feeding a bear in its cage, Huckins was chased and killed. The bear also injured three other people, and was eventually shot and killed with thirteen gun shots.[201]",Black,0,0,0,1 +Rudolph Gaier,50,male,19/11/1952,11,1952,Wild,"near Anchorage, Alaska","Gaier and a black bear were found dead at a remote mountain cabin. An investigator concluded that Gaier shot the bear after it entered his cabin, and before dying, the bear fatally clawed Gaier.[199][200]",Black,0,0,0,1 +Andrew Mark Palmer,3,male,11/08/1953,8,1953,Captive,"Flagstaff, Arizona","While Palmer was playing with his grandparents' pet bear, he was mauled. The bear was shot and killed by a neighbor.[198]",Black,0,0,0,1 +Willies McBride,NA,male,19/09/1955,9,1955,Wild,"near Eureka, Alaska",McBride was mauled while hunting alone. The bear was not found.[206],Brown,1,0,0,1 +Paul Lemery,28,male,09/10/1956,10,1956,Captive,"Libertyville, Illinois","Lemery, an animal trainer, was attacked when taking a bear out of its cage. He was preparing for a television appearance with the bear.[205]",Brown,0,0,0,1 +Kenneth Scott,29,male,22/10/1956,10,1956,Wild,"near Augusta, Montana","While elk hunting, a hunter in Scott's group was attacked, and the bear was shot and wounded. When they went back to kill the bear, Scott's gun jammed and the bear mauled him. The bear was later killed by another hunter.[203][204]",Brown,1,0,0,1 +Barbara Coates,7,female,12/08/1958,8,1958,Wild,"Jasper National Park, Alberta","While Coates was picking berries outside of her family's Sunwapta Falls cottage, a black bear appeared. Coates ran to the cottage, but the bear chased and mauled her.[197]",Black,0,0,0,1 +Sam Adams,45,male,27/10/1958,10,1958,Wild,"near Ovando, Montana","Adams was missing after hunting near the Continental Divide northeast of Missoula. His body was found smashed in three parts. Laboratory studies showed evidence that Adams was in a fight with the bear, which was described as ""probably a grizzly"".[202]",Brown,1,1,0,1 +Lyndon Hooper,51,male,06/09/1959,9,1959,Wild,"near Cadomin, Alberta","Hooper was fishing alone when attacked 20 miles (32 km) from Cadomin, Alberta. His mutilated body was found in a stream.[194] Three days later, a forest ranger shot a bear .5 miles (0.80 km) from where Hooper's body was recovered.[195] It was later discovered that the bear's stomach contained human hair.[196]",Black,0,0,0,1 +William Strandberg,51,male,16/08/1963,8,1963,Wild,"near Fairbanks, Alaska",A bear killed Strandberg approximately 160 miles (260 km) west of Fairbanks. Strandberg was a member of a prominent Alaskan mining family.[187][188],Black,0,0,0,1 +Sidney Smith,26,male,17/09/1964,9,1964,Wild,"near Schefferville, Quebec","Smith, a technician on a radar line, was attacked by a black bear in a remote area. There was evidence that Smith tried to defend himself with a hunting knife.[186]",Black,1,0,0,1 +Vernon Sauvola,51,male,02/07/1965,7,1965,Wild,"Aitkin, Minnesota","Sauvola was attacked while he was fishing in a stream, and his body was dragged 60 feet (18 m).[185]",Black,0,0,0,1 +Phyllis Tremper,3,female,07/09/1966,9,1966,Captive,"Prescott, Arizona","A pet bear dragged Tremper into its cage at the Ponderosa Trailer Park in Prescott, Arizona. The bear's owner shot and killed it.[184]",Black,0,0,0,1 +Susan Duckitt,11,female,08/08/1967,8,1967,Wild,"near Okanagan Landing, British Columbia","Duckitt and a friend were picnicking by Okanagan Lake. They went on a walk up a hill and encountered the bear standing on its hind feet. The girls ran away, and Duckitt was caught. A man tracked down the bear and killed it with six shots.[183]",Black,0,0,0,1 +Julie Helgeson,19,female,13/08/1967,8,1967,Wild,"Glacier National Park, Montana","While camping near the Granite Park Chalet, Helgeson was dragged from her tent. Her boyfriend was also severely mauled. This incident became widely known as ""Night of the Grizzlies"" when two young women were separately attacked in Glacier National Park, Montana, by grizzly bears.[190][191]",Brown,0,1,0,0 +Michele Koons,19,female,13/08/1967,8,1967,Wild,"Glacier National Park, Montana","Koons was camping with a group at the Trout Lake campsite. A bear invaded their camp, and while other campers climbed up trees, Koons was caught in her sleeping bag, and attacked. This incident became widely known as ""Night of the Grizzlies"" when two young women were separately attacked in Glacier National Park, Montana, by grizzly bears.",Brown,0,1,0,0 +Jack Ottertail,53,male,01/10/1968,10,1968,Wild,"near Atikokan, Ontario",Ottertail was killed while on a walk. A bear found near the body was shot and killed.[182],Black,0,0,0,1 +Paulosie Meeko,19,male,17/11/1968,11,1968,Wild,"Churchill, Manitoba","Meeko's throat was slashed by a polar bear, and he died less than two hours after the attack. The bear was shot by the police.[193]",Polar,0,0,0,1 +Russell Ringer,49,male,31/03/1969,3,1969,Captive,"Fort Leonard Wood, Missouri","Ringer was crushed by his pet bear, which had no teeth or claws, as he entered its cage for a wrestling match at the military base.[189]",Brown,0,0,0,1 +Harvey Cardinal,40,male,15/01/1970,1,1970,Wild,"near Fort St. John, British Columbia","Cardinal was attacked and partially eaten while hunting near the Doig River. The bear had a gum infection, and was shot and killed.[175]",Brown,1,0,0,1 +John Richardson,31,male,25/07/1971,7,1971,Wild,"near Rocky Mountain National Park, Colorado","Richardson was attacked while camping on private property, just west of Rocky Mountain National Park, and north of Grand Lake. The bear was later killed by a professional hunter. This was the first fatal black bear attack in Colorado in modern times.[161]",Black,1,0,0,1 +Richard Hale,19,male,19/01/1972,1,1972,Captive,"Toledo, Ohio",Hale's body was found at the bottom of the polar bear grotto at the Toledo Zoo. There was evidence that Hale was under the influence of drugs at the time of his attack.[180][181],Polar,0,0,0,1 +Harry Walker,25,male,25/06/1972,6,1972,Wild,"Yellowstone National Park, Wyoming",Walker was attacked by a bear that was feeding on food that was left out at his campsite near Old Faithful Inn.[174],Brown,0,0,0,1 +Wilf Etherington,51,male,25/09/1973,9,1973,Wild,"Banff National Park, Alberta","Etherington, a biologist with the Canadian Wildlife Service, and a photographer were helping with the relocation of a troublesome grizzly bear in Banff National Park. The bear had been recently trapped and sedated. When the two men approached the bear, it charged and attacked Etherington.[172][173]",Brown,0,1,0,1 +Victoria Valdez,4,female,16/05/1974,5,1974,Wild,"Glenwood, Washington",Valdez was mauled while playing near her home. Her body was found 200 yards (180 m) from her home. Her father shot and killed a 250 pounds (110 kg) bear before finding his daughter's body.[160],Black,0,0,0,1 +Jay Reeves,38,male,01/08/1974,8,1974,Wild,"Izembek National Wildlife Refuge, Alaska","Reeves was camping alone on the Alaskan Peninsula, near Cold Bay. A fisherman discovered a camp that looked like it was damaged by a bear, and found only Reeves' shoes. A helicopter spotted and shot a grizzly bear near the camp. Later, they found Reeves' remains, and an autopsy on the bear revealed human remains.[171]",Brown,0,1,0,1 +Richard Pernitzky,18,male,05/01/1975,1,1975,Wild,"Inuvik, Northwest Territories",Pernitzky was mauled at an Imperial Oil exploration site. The bear was later shot and killed.[179],Polar,0,0,0,1 +Mary Ann Johns,1,female,12/08/1975,8,1975,Captive,"Stewardson, Illinois","While carnival workers were setting up, a bear was taken out of its cage and chained to a tree. Johns, whose parents were carnival workers, walked by and was attacked. The bear had previously attacked children.[159]",Black,0,0,0,1 +Barbara Chapman,24,female,24/07/1976,7,1976,Wild,"Glacier National Park of Canada, British Columbia","While hiking with a friend in British Columbia's Glacier National Park, Chapman rounded a bend to find a grizzly bear charging. The bear first attacked Chapman's friend, who initially resisted, but left him alone after he played dead. The bear then attacked Chapman, who fought back and was quickly killed. Chapman's friend sustained serious injuries, but was able to hike out for help. The grizzly bear that attacked and her three cubs were soon found and killed.[169][170]",Brown,0,1,1,1 +Lafayette Herbert,43,male,26/08/1976,8,1976,Captive,"Baltimore, Maryland","Herbert, who had a history of mental illness, was killed after he climbed into the polar bear enclosure at the Baltimore Zoo.[176][177][178]",Polar,0,0,0,1 +Mary Pat Mahoney,22,female,23/09/1976,9,1976,Wild,"Glacier National Park, Montana",Mahoney was dragged from a tent and killed at Many Glacier campground. Rangers killed two grizzly bears in the area a few hours after the attack.[166][167],Brown,0,1,0,1 +Alison Muser,5,female,01/07/1977,7,1977,Wild,"Waterton Lakes National Park, Alberta",Muser was mauled to death by a black colored Grizzly Bear while playing with her sister near Cameron Creek in Waterton Lakes National Park. She died en route to a Calgary hospital. The family had just recently moved to Canada from South Africa and was unaware of the danger posed by bears. The family threatened legal action against the park for failing to provide warnings of the dangers posed by the animals.[164] The bear responsible for the attack was killed.[165],Brown,0,1,0,1 +Mark Halfkenny,12,male,13/05/1978,5,1978,Wild,"Algonquin Provincial Park, Ontario",The three boys were stalked and killed while fishing near Radiant Lake in Algonquin Provincial Park. This was the first fatal bear attack in the park in eighty years.,Black,0,0,0,0 +Billy Rhindress,15,male,13/05/1978,5,1978,Wild,"Algonquin Provincial Park, Ontario",The three boys were stalked and killed while fishing near Radiant Lake in Algonquin Provincial Park. This was the first fatal bear attack in the park in eighty years.,Black,0,0,0,0 +George Halfkenny,16,male,13/05/1978,5,1978,Wild,"Algonquin Provincial Park, Ontario",The three boys were stalked and killed while fishing near Radiant Lake in Algonquin Provincial Park. This was the first fatal bear attack in the park in eighty years.,Black,0,0,0,0 +Lynn Orser,30,female,02/07/1978,7,1978,Captive,"King, Ontario","A bear trained to wrestle humans entered its owner's home and attacked the owner's friend, Orser, in her bedroom.[157]",Black,0,0,0,1 +Monty Adams,32,male,15/09/1979,9,1979,Wild,"near Pincher Creek, Alberta","While hunting alone for sheep west of Pincher Creek in Southern Alberta, Adams was mauled by a grizzly bear. Adams was found by two other hunters, and died when rescuers were removing him from the area.[162][163]",Brown,1,1,0,1 +Allan Russell Baines,10,male,18/07/1980,7,1980,Wild,"near Granisle, British Columbia",Baines was killed on a fishing trip with two friends.[136],Black,0,0,0,1 +Jane Ammerman,19,female,24/07/1980,7,1980,Wild,"Glacier National Park, Montana",Their partially consumed bodies were found near their campsite at Divide Creek in the St. Mary Valley. The bear was later killed by Native American hunters.[152][153],Brown,1,0,0,0 +Kim Eberly,19,male,24/07/1980,7,1980,Wild,"Glacier National Park, Montana",Their partially consumed bodies were found near their campsite at Divide Creek in the St. Mary Valley. The bear was later killed by Native American hunters.[152][153],Brown,1,0,0,0 +Carol Marshall,24,female,14/08/1980,8,1980,Wild,"near Zama City, Alberta",Morris and Marshall were killed by the same bear in separate attacks over a span of two hours. They were working at a remote oil drilling camp.[135],Black,0,0,0,0 +Lee Randal Morris,44,male,14/08/1980,8,1980,Wild,"near Zama City, Alberta",Morris and Marshall were killed by the same bear in separate attacks over a span of two hours. They were working at a remote oil drilling camp.[135],Black,0,0,0,0 +Ernest Cohoe,38,male,24/08/1980,8,1980,Wild,"near Banff, Alberta","While fishing with a friend just north of Banff, Alberta, a bear charged and bit off part of Cohoe's face. He died a week later as a result of the injuries.[150][151]",Brown,0,0,0,1 +Laurence Gordon,33,male,30/09/1980,9,1980,Wild,"Glacier National Park, Montana","Gordon was killed at the Elizabeth Lake campsite in the Belly River Valley, Glacier National Park.[148][149]",Brown,0,0,0,1 +Conrado Mones,29,male,27/09/1982,9,1982,Captive,New York City,Mones was mauled after climbing three fences in New York City's Central Park Zoo to enter the bear's pen.[156],Polar,0,0,0,1 +Clifford David Starblanket,26,male,21/05/1983,5,1983,Wild,"near Canwood, Saskatchewan","Starblanket, a trapper living in the forest, suffered an attack to his throat and head.[133][134]",Black,0,0,0,1 +Melvin Rudd,55,male,27/05/1983,5,1983,Wild,"near Nipawin Provincial Park, Saskatchewan",Rudd was killed while fishing in central Saskatchewan.[133][134],Black,0,0,0,1 +Roger May,23,male,25/06/1983,6,1983,Wild,"Gallatin National Forest, Montana","May was dragged from his tent, and eaten at the Rainbow Point campground, northwest of Yellowstone National Park. The bear was captured and killed with an injection of poison.[146][147]",Brown,0,0,0,1 +Daniel Anderson,12,male,06/07/1983,7,1983,Wild,"La Vérendrye Wildlife Reserve, Quebec",Anderson was grabbed from his tent while camping. His body was found 100 feet (30 m) from the tent.[132],Black,0,0,0,1 +Thomas Mutanen,46,male,29/11/1983,11,1983,Wild,"Churchill, Manitoba","Mutanen was attacked and dragged on a street in Churchill. The bear was part of an annual migration to Hudson Bay. Due to a lack of ice on the bay, the bear wandered into the town.[155]",Polar,0,0,0,1 +Brigitta Fredenhagen,25,female,30/07/1984,7,1984,Wild,Yellowstone National Park,Fredenhagen was dragged from her tent during the night and killed at a backcountry campsite at the southern end of White Lake in Yellowstone National Park.[144][145],Brown,0,0,0,1 +Gordon Ray,24,male,29/05/1985,5,1985,Wild,"near Fort Nelson, British Columbia","Ray was killed while on a tree planting project approximately 45 kilometres (28 mi) south of Fort Nelson. He climbed a tree to avoid the bear, but fell, and was attacked. The bear was later shot by a helicopter pilot.[131]",Black,0,0,0,1 +Alan Precup,25,male,11/09/1986,9,1986,Wild,"Glacier Bay National Park and Preserve, Alaska","Precup did not return after backpacking in Glacier Bay National Park and Preserve. Days later, searchers found his campsite with his bare skeleton, one intact hand, and both feet still booted.[168]",Brown,0,0,0,1 +William Tesinsky,38,male,05/10/1986,10,1986,Wild,"Yellowstone National Park, Wyoming","Tesinkey, a photographer, was mauled after approaching a bear in the Otter Creek area of Hayden Valley, Yellowstone National Park. The bear was killed.[142][143]",Brown,0,0,0,1 +Charles Gibbs,40,male,25/04/1987,4,1987,Wild,"Glacier National Park, Montana",Gibbs was last seen alive following and photographing a bear with cubs at Elk Mountain in Glacier National Park. Investigators recovered film of the female approaching in attack mode at 50 yards (46 m).[140][141],Brown,0,0,0,1 +Juan Perez,11,male,19/05/1987,5,1987,Captive,"Brooklyn, New York","Perez was killed by two bears after climbing a fence in Prospect Park Zoo, Brooklyn, New York. The bears were killed by police officers.[154]",Polar,0,0,0,1 +Gary Goeden,29,male,28/07/1987,7,1987,Wild,"Glacier National Park, Montana","Goeden's partially consumed remains were found at Natahki Lake, Many Glacier Valley, Glacier National Park. He was on a solo hike, and off-trail.[139]",Brown,0,0,1,1 +Harley Seivenpiper,40,male,04/11/1988,11,1988,Wild,"Port Alexander, Alaska","Seivenpiper was killed while hunting alone. The bear dragged Seivenpiper's body almost 1-mile (1.6 km) uphill to a cache. When searchers approached the cache, the bear charged, and was shot and killed.[137][138]",Brown,1,0,0,1 +Carl Stalker,28,male,08/12/1990,12,1990,Wild,"Point Lay, Alaska","While Stalker was walking with his girlfriend, he was chased and consumed in the middle of the town. The bear was shot and killed near Stalker's corpse.[130]",Polar,0,0,0,1 +James Waddell,12,male,26/05/1991,5,1991,Wild,"Lesser Slave Lake, Alberta","In the Marten River Campground, Waddel was dragged from a tent during the night and killed.[108]",Black,0,0,0,1 +Raymond Jakubauskas,32,male,11/10/1991,10,1991,Wild,"Algonquin Provincial Park, Ontario","While they were setting up camp on Bates Island, a black bear broke both of their necks. The bear then dragged their bodies into the woods and consumed the remains. When police arrived five days later, the bear was guarding the bodies. A park naturalist called the attack ""right off the scale of normal bear behavior"".[106][107]",Black,0,0,0,0 +Carola Frehe,48,female,11/10/1991,10,1991,Wild,"Algonquin Provincial Park, Ontario","While they were setting up camp on Bates Island, a black bear broke both of their necks. The bear then dragged their bodies into the woods and consumed the remains. When police arrived five days later, the bear was guarding the bodies. A park naturalist called the attack ""right off the scale of normal bear behavior"".[106][107]",Black,0,0,0,0 +Sébastien Lauzier,20,male,14/06/1992,6,1992,Wild,"near Cochrane, Ontario","Lauzier was attacked while taking soil samples. Lauzier's partner, Rod Barber, was able to drive off the bear with a pole and was not hurt. The incident occurred about 92 km (57 miles) northeast of Cochrane, just west of the Quebec border.[105]",Black,0,0,0,1 +Darcy Staver,33,female,08/07/1992,7,1992,Wild,"Glennallen, Alaska","The bear entered her cabin and Staver and her husband fled to the roof. While Staver's husband went for help, the bear killed her. The bear was shot and killed by a neighbor.[103][104]",Black,0,0,0,1 +Anton Bear,6,male,10/07/1992,7,1992,Wild,"near King Cove, Alaska","The six-year-old, his mother, and sister were walking down a road when they were approached by a grizzly that had just been feeding at the town dump. The family fled, but the boy was chased down by the bear and killed. The bear devoured most of the victim before villagers could kill the animal.[127]",Brown,0,1,0,1 +Trevor Percy-Lancaster,40,male,15/09/1992,9,1992,Wild,"Jasper National Park, Alberta","Percy-Lancaster and his wife were setting up camp in an isolated area of the Tonquin Valley. They surprised a bear, and began running away. The bear initially caught Percy-Lancaster's wife, and then he distracted the bear, which turned on him.[125][126]",Brown,0,0,0,1 +John Petranyi,40,male,03/10/1992,10,1992,Wild,"Glacier National Park, Montana","Petranyi was killed by a mother with two cubs on the Loop Trail, near the Granite Park Chalet.[124]",Brown,0,0,0,1 +Colin McClelland,24,male,10/08/1993,8,1993,Wild,"Fremont County, Colorado","A bear tore open the door to McClelland's trailer and attacked him at Waugh Mountain, Colorado. The bear was later killed by game wardens.[55]",Black,0,0,0,1 +Ian Dunbar,4,male,16/09/1994,9,1994,Wild,"70 Mile House, British Columbia",Dunbar was attacked in the back yard of his home. The bear was later killed by conservation officers.[102],Black,0,0,0,1 +Larry Waldron,45,male,01/07/1995,7,1995,Wild,"near Anchorage, Alaska","Trent and her son Waldron were killed by a bear defending a moose carcass while they were hiking on the McHugh Creek Trail in Chugach State Park, near Anchorage, Alaska.[3][123]",Brown,0,0,0,0 +Marcie Trent,77,female,01/07/1995,7,1995,Wild,"near Anchorage, Alaska","Trent and her son Waldron were killed by a bear defending a moose carcass while they were hiking on the McHugh Creek Trail in Chugach State Park, near Anchorage, Alaska.[3][123]",Brown,0,0,0,0 +Shane Fumerton,32,male,09/10/1995,10,1995,Wild,"near Radium Hot Springs, British Columbia","Fumerton and Caspell were killed while securing an elk in the vicinity of Mount Soderhome, in the Southern Rocky Mountain Trench in southeastern British Columbia.[121][122]",Brown,0,0,0,0 +Bill Caspell,40,male,09/10/1995,10,1995,Wild,"near Radium Hot Springs, British Columbia","Fumerton and Caspell were killed while securing an elk in the vicinity of Mount Soderhome, in the Southern Rocky Mountain Trench in southeastern British Columbia.[121][122]",Brown,0,0,0,0 +Sevend Satre,53,male,14/06/1996,6,1996,Wild,"near Tatlayoko Lake, British Columbia","Satre was killed while checking fence lines near the central British Columbia community of Tatlayoko Lake, British Columbia.[99]",Black,0,0,0,1 +Christine Courtney,32,female,05/07/1996,7,1996,Wild,"Kluane National Park, Yukon",Courtney was killed while hiking on the Slim's Valley trail in Kluane National Park. Her husband was also attacked but survived. Park wardens killed the bear.[120],Brown,0,0,0,1 +Robert Bell,33,male,23/08/1996,8,1996,Wild,"Gates of the Arctic National Park, Alaska",Bell was killed while hiking with a friend near the Kugrak river. They startled a mother bear feeding on salmon.[119],Brown,0,0,0,1 +Patti McConnell,37,female,14/08/1997,8,1997,Wild,"Liard River Hot Springs Provincial Park, British Columbia","McConnell died from injuries while defending herself and her son from a black bear attack on a boardwalk to the hot springs. Kitchen heard the attack in progress, and was killed while attempting to rescue. McConnell's son and a 20-year-old man were also injured. The bear was shot while standing over the victims.[99][100]",Black,0,0,0,0 +Raymond Kitchen,56,male,14/08/1997,8,1997,Wild,"Liard River Hot Springs Provincial Park, British Columbia","McConnell died from injuries while defending herself and her son from a black bear attack on a boardwalk to the hot springs. Kitchen heard the attack in progress, and was killed while attempting to rescue. McConnell's son and a 20-year-old man were also injured. The bear was shot while standing over the victims.[99][100]",Black,0,0,0,0 +Audelio Luis Cortes,40,male,08/02/1998,2,1998,Wild,"near Kenai, Alaska",Cortes was killed immediately after being bitten in the head while laying seismic line in the Swanson River area. His crew walked past the bear's den.[117][118],Brown,0,0,0,1 +Craig Dahl,26,male,17/05/1998,5,1998,Wild,"Glacier National Park, Montana",Dahl's partially consumed remains were found three days after he set off to hike alone in the Two Medicine area of Glacier National Park. He was attacked by a mother and her two cubs.[116],Brown,0,0,1,1 +Christopher Kress,40,male,22/08/1998,8,1998,Wild,"near Beaver Mines, Alberta","Kress was killed by a grizzly bear while fishing on the South Castle River, near the Beaver Mines campground in Alberta.[114][115]",Brown,0,1,0,1 +George Evanoff,65,male,24/10/1998,10,1998,Wild,"near Prince George, British Columbia","Evanoff was hiking on the Bearpaw Ridge, 72 kilometres (45 mi) northeast of Prince George, British Columbia. He encountered a grizzly feeding on a moose kill about a half-mile (800 m) from his cabin. He was bitten on the neck, but his body was not mauled or eaten by the bear.[112][113]",Brown,0,1,0,1 +Ken Cates,53,male,25/05/1999,5,1999,Wild,"Kenai National Wildlife Refuge, Alaska","Cates was killed while hiking near Soldotna, Alaska in the Kenai National Wildlife Refuge. Troopers found Cates' rifle, spent shell casings, and blood nearby which suggested that Cates may have shot the bear.[110][111]",Brown,0,0,0,1 +Hattie Amitnak,64,female,09/07/1999,7,1999,Wild,"near Rankin Inlet, Nunavut","Amitnak was mauled after trying to distract a bear that attacked and injured two other people at a Hudson Bay camp.[128] She was later awarded a posthumous medal of bravery by then-Governor-General of Canada, Adrienne Clarkson.[129]",Polar,0,0,0,1 +Ned Rasmussen,53,male,01/11/1999,11,1999,Wild,"Uganik Island, Alaska","After Rasmussen disappeared on a deer hunting trip, he was found dead.[109]",Brown,1,0,0,1 +Glenda Ann Bradley,50,female,21/05/2000,5,2000,Wild,"Great Smoky Mountains National Park, Tennessee","Bradley was attacked and partially consumed by a mother bear and a cub, 1.5 miles (2.4 km) upstream from Elkmont, Tennessee. It was the first fatal bear attack in a southeastern U.S. National Park. While hovering over Bradley's corpse, the bears were shot and killed by park rangers.[1][79]",Black,0,0,0,1 +Mary Beth Miller,24,female,02/07/2000,7,2000,Wild,"near Valcartier, Quebec",Miller was attacked while on a biathlon training run in a wooded area on a military base. The bear was trapped and killed four days later.[1][78],Black,0,0,0,1 +George Tullos,41,male,14/07/2000,7,2000,Wild,"Hyder, Alaska",Tullos' partially consumed body was found at a campground near the Canada–US border in Southeast Alaska. The bear was shot and killed.[98],Brown,0,0,0,1 +Kyle Harry,18,male,03/06/2001,6,2001,Wild,"near Yellowknife, Northwest Territories","Harry was attacked while with a group at a rural campsite 25 kilometres (16 mi) east of Yellowknife in the Northwest Territories, Canada.[77]",Black,0,0,0,1 +Adelia Maestras Trujillo,93,female,18/08/2001,8,2001,Wild,"Mora, New Mexico",A bear broke through a glass pane to gain entry into Trujillo's house and killed her. Trujillo's body was found in her kitchen. The bear was shot .5 miles (0.80 km) from the house.[76],Black,0,0,0,1 +Timothy Hilston,50,male,30/10/2001,10,2001,Wild,"near Ovando, Montana","Hilston was attacked as he field dressed an elk in Western Montana.[95] A female bear and her cubs suspected in the attack were killed by U.S. Fish and Wildlife officials.[96] Hilston's widow sued federal and state agencies for negligence, and the lawsuits were dismissed by District Court judge Donald W. Molloy.[97]",Brown,0,0,0,1 +Ester Schwimmer,0.416666667,female,19/08/2002,8,2002,Wild,"Fallsburg, New York","A bear knocked Schwimmer from her stroller, which was near the porch of her family's vacation home. The bear carried the infant in its mouth to the woods. Schwimmer died of neck and head injuries.[75]",Black,0,0,0,1 +Christopher Bayduza,31,male,01/09/2002,9,2002,Wild,"near Fort Nelson, British Columbia","After going for a walk behind a trailer, Bayduza was attacked at a remote oil rigging site in northeastern British Columbia.[73][74]",Black,0,0,0,1 +Maurice Malenfant,77,male,29/09/2002,9,2002,Wild,"Saint-Zénon-du-Lac-Humqui, Quebec",Malenfant was attacked in his campsite in the Gaspé region of Quebec.[71][72],Black,0,0,0,1 +Amie Huguenard,37,female,05/10/2003,10,2003,Wild,"Katmai National Park, Alaska",Treadwell and Huguenard's corpses were found by their pilot at Kaflia Bay. Treadwell was famous for his books and documentaries on living with wild bears in Alaska. State Troopers investigating the incident recovered an audiotape of the attack. The two were killed on the last night before their scheduled pickup after spending several months in the Alaskan bush.[94] The attack is chronicled in the 2005 American documentary film Grizzly Man by German director Werner Herzog.,Brown,0,1,0,0 +Timothy Treadwell,46,male,05/10/2003,10,2003,Wild,"Katmai National Park, Alaska",Treadwell and Huguenard's corpses were found by their pilot at Kaflia Bay. Treadwell was famous for his books and documentaries on living with wild bears in Alaska. State Troopers investigating the incident recovered an audiotape of the attack. The two were killed on the last night before their scheduled pickup after spending several months in the Alaskan bush.[94] The attack is chronicled in the 2005 American documentary film Grizzly Man by German director Werner Herzog.,Brown,0,1,0,0 +Isabelle Dubé,35,female,05/06/2005,6,2005,Wild,"Canmore, Alberta","Dubé was killed while jogging with two friends on the Bench Trail. After an initial attack, Dubé climbed a tree while her friends sought help. The bear brought Dubé down from the tree and mauled her.[91][92]",Brown,0,0,0,1 +Merlyn Carter,71,male,14/06/2005,6,2005,Wild,"Nonacho Lake, Northwest Territories","Carter was found dead behind the main cabin of his fishing camp. Carter's son came to the cabin the day after the attack, and shot and killed the bear.[70]",Black,0,0,0,1 +Kathy Huffman,58,female,23/06/2005,6,2005,Wild,"Arctic National Wildlife Refuge, Alaska","The Huffmans were attacked while in their tent at a campsite along the Hulahula River 12 miles (19 km) upriver from Kaktovik.[89] Two days later the campsite was discovered by three rafters while the bear was still nearby. The bear chased the rafters down the river for over half a mile (800 m) until it finally gave up. Later, a North Slope Borough Police officer investigating the scene shot and killed the bear at the campsite.[90]",Brown,0,0,0,0 +Rich Huffman,61,male,23/06/2005,6,2005,Wild,"Arctic National Wildlife Refuge, Alaska","The Huffmans were attacked while in their tent at a campsite along the Hulahula River 12 miles (19 km) upriver from Kaktovik.[89] Two days later the campsite was discovered by three rafters while the bear was still nearby. The bear chased the rafters down the river for over half a mile (800 m) until it finally gave up. Later, a North Slope Borough Police officer investigating the scene shot and killed the bear at the campsite.[90]",Brown,0,0,0,0 +Harvey Robinson,69,male,26/08/2005,8,2005,Wild,"Selkirk, Manitoba","Robinson was fatally mauled while picking plums north of Winnipeg, Manitoba. Robinson's family were investigating the area with an RCMP officer later that day, and were also attacked. The officer shot and killed the bear.[69]",Black,0,0,0,1 +Jacqueline Perry,30,female,06/09/2005,9,2005,Wild,"Missinaibi Lake Provincial Park, Ontario","Perry was killed in an attack at a remote campsite.[66] Her husband was seriously injured trying to protect her with a Swiss Army knife, and later was given a Star of Courage award from Governor General Michaëlle Jean.[67] Ministry of Natural Resources staff shot and killed the bear near the area where the fatal attack occurred.[68]",Black,0,0,0,1 +Arthur Louie,60,male,20/09/2005,9,2005,Wild,"near Bowron River, British Columbia",A female and two cubs attacked Louie on a remote forestry road. He was walking back to his gold mining camp after his car broke down.[87][88],Brown,0,0,0,1 +Elora Petrasek,6,female,13/04/2006,4,2006,Wild,"Cherokee National Forest, Tennessee","A bear attacked the family at a waterfall near a campground. Petrasek's mother and brother were also injured. The bear was trapped and killed, and an unrelated bear was mistakenly killed.[63][64][65]",Black,0,0,0,1 +Jean-François Pagé,28,male,28/04/2006,4,2006,Wild,"near Ross River, Yukon",Pagé was mauled while staking mineral claims. He unknowingly walked right past a bear den containing a sow and two cubs.[86],Brown,0,0,0,1 +Samuel Evan Ives,11,male,17/06/2007,6,2007,Wild,"Uinta National Forest, Utah","Ives was grabbed from a family tent in American Fork Canyon, and mauled. State wildlife officials killed the bear, which had entered the campsite the night before.[59] Ives' family sued the U.S. Forest Service because there was no warning about the bear's presence.[60][61] A judge awarded the family $1.95 million.[62] It was the first known fatal black bear attack in Utah.[61]",Black,0,0,0,1 +Robin Kochorek,31,female,20/07/2007,7,2007,Wild,"Panorama Mountain Resort, British Columbia",Kochorek was reported missing after mountain biking. A black bear was found near her corpse the morning after her disappearance. The bear was shot on sight by the Royal Canadian Mounted Police(RCMP).[58],Black,0,0,0,1 +Don Peters,51,male,25/11/2007,11,2007,Wild,"near Sundre, Alberta",Peters' body was found 200 metres (660 ft) from his parked truck. He was on a hunting trip. An autopsy confirmed that he died due to a grizzly bear attack. The bear that attacked Peters was captured and killed the following April.[84][85],Brown,1,1,0,1 +Stephan Miller,39,male,22/04/2008,4,2008,Captive,"Big Bear Lake, California","Rocky, bear trained to perform in movies turned on its handler, fatally biting him in the neck. Prior to the attack, the bear was featured in the movie Semi-Pro. Pepper spray was used to subdue the bear.[82][83]",Brown,0,0,0,1 +Cécile Lavoie,70,female,30/05/2008,5,2008,Wild,"near La Sarre, Quebec","After Lavoie didn't return to her cabin following a solo fishing outing, her husband went looking for her. He found a bear dragging her body into the woods.[57]",Black,0,0,0,1 +Robert Wagner,48,male,01/10/2008,10,2008,Wild,"near Sundre, Alberta","Wagner was reported missing after not returning from a hunting trip. His body was found less than 1 kilometre (0.62 mi) from his parked truck. An autopsy revealed that he had been killed by a grizzly bear, which was shot by wildlife officers.[80][81]",Brown,1,1,0,1 +Donna Munson,74,female,06/08/2009,8,2009,Wild,"Ouray, Colorado","Munson had been feeding bears for a decade, and was repeatedly warned by wildlife officials. After a bear was injured in a fight with an older and bigger bear, Munson left food out to help the injured bear. The older bear came back to Munson's property, forced its way past a wire fence, and mauled Munson. Later, wildlife officials killed two bears on Munson's property. One of the bears had a necropsy which revealed evidence that it consumed Munson.[55][56]",Black,0,0,0,1 +Kelly Ann Walz,37,female,04/10/2009,10,2009,Captive,"Ross Township, Pennsylvania","Walz, whose husband had an expired license to keep exotic animals, was attacked while cleaning her pet bear's cage. She tried to distract the bear by throwing dog food to the opposite end of the cage. A neighbor shot and killed the bear.[54]",Black,0,0,0,1 +Erwin Frank Evert,70,male,17/06/2010,6,2010,Wild,"Shoshone National Forest, Wyoming","Evert, a field botanist, was mauled by a grizzly bear while hiking in the Kitty Creek Drainage area of the Shoshone National Forest, just east of Yellowstone National Park. The bear was trapped and tranquilized earlier in the day by a grizzly bear research team. Two days after the attack, the bear was shot and killed from a helicopter by wildlife officials.[47]",Brown,0,1,0,1 +Kevin Kammer,48,male,28/07/2010,7,2010,Wild,"Gallatin National Forest, Montana","Kammer was in his tent at Soda Butte Campground when a mother bear attacked and dragged him 25 feet (7.6 m) away. Two other campers in separate campsites were also attacked: a teenager was bitten in the leg, and a woman was bitten in the arm and leg. The bear was caught in a trap set at the campground using pieces of a culvert and Kammer's tent.[45] Later, the bear was killed, and her cubs were sent to ZooMontana.[46] The mother bear's unusual predatory behavior was noted by authorities.[46]",Brown,0,0,0,1 +Brent Kandra,24,male,19/08/2010,8,2010,Captive,"Columbia Station, Ohio","Kandra was a bear caretaker on property of Sam Mazzola that kept exotic pets. The bear was out of its cage for feeding. Prior to the attack, Sam Mazzola had his license to exhibit animals revoked, but was still allowed to keep the animals on his property.[19] He also accumulated dozens of dangerous, exotic animals despite past convictions and losing his license after animal rights activists complained he was making money by letting people wrestle bears.",Black,0,0,0,1 +Bernice Adolph,72,female,01/06/2011,6,2011,Wild,"near Lillooet, British Columbia","Adolph's remains were found by police dogs after she was reported missing. She was an elder in the Xaxli'p First Nation. There was evidence that bears fed on Adolph's remains, and tried to enter her house. An autopsy confirmed that she died from a bear attack. Five bears suspected of being involved were killed by conservation officers, and DNA tests confirmed that one of the dead bears killed Adolph.[18]",Black,0,0,0,1 +Brian Matayoshi,57,male,06/07/2011,7,2011,Wild,"Yellowstone National Park, Wyoming","Matayoshi and his wife were hiking the Wapiti Lake Trail, and came upon a mother grizzly bear in an open meadow. The couple began to walk away, and the bear charged. After attempting to run away, Matayoshi was fatally bitten and clawed. Matayoshi's wife hid behind a tree, was lifted from the ground by the bear, and dropped. She played dead, and the bear left the area. She was not injured.[42][43]",Brown,0,1,0,1 +Lana Hollingsworth,61,female,25/07/2011,7,2011,Wild,"Pinetop-Lakeside, Arizona","Hollingsworth was attacked by a 250 lb (113.4 kg) black bear while walking her dog at a country club. Nearly a month later and after eleven surgeries, she died from a massive brain hemorrhage, which doctors believe was a result of the attack. The bear was tracked, shot, and killed.[17]",Black,0,0,0,1 +John Wallace,59,male,24/08/2011,8,2011,Wild,"Yellowstone National Park, Wyoming","Wallace's remains were found by hikers on the Mary Mountain Trail, northeast of Old Faithful.[37] Wallace was hiking alone.[38]An autopsy showed that Wallace died from a bear attack.[38] According to a report released by Yellowstone rangers, park officials had attempted to give Wallace a lecture about bear safety, but he was not interested, calling himself a ""grizzly bear expert"".[39]",Brown,0,1,1,1 +Richard White,49,male,24/08/2012,8,2012,Wild,"Denali National Park, Alaska","White was backpacking alone along the Toklat River. After hikers found an abandoned backpack and torn clothing, rangers investigated and found a male grizzly bear sitting on White's remains. The bear was shot and killed by an Alaska State Trooper. A necropsy of the bear and photographs recovered from White's camera confirmed the attack.[34]",Brown,0,1,1,1 +Tomas Puerta,54,male,01/10/2012,10,2012,Wild,"Chichagof Island, Alaska","After passers-by spotted an unattended skiff, they investigated and encountered a grizzly bear sow and two cubs. Alaska State troopers and Sitka Mountain rescue personnel then found evidence of a campsite and fire on the beach. There was evidence of a struggle, and upon following a trail of disturbed vegetation, they found Puerta's body, cached and partially eaten.[36]",Brown,0,1,0,1 +Robert Weaver,64,male,06/06/2013,6,2013,Wild,"near Delta Junction, Alaska","Weaver was attacked by a black bear while walking back to his cabin on George Lake, according to his wife, who was able to flee inside the cabin and was uninjured. A 230 lb (104.3 kg) adult male black bear on the scene was killed by troopers and found to have some of Weaver's remains in his stomach.[16]",Black,0,0,0,1 +Lorna Weafer,36,female,07/05/2014,5,2014,Wild,"near Fort McMurray, Alberta","Weafer, a Suncor worker was attacked at the remote North Steepbank oil sands mine site while walking back to work after a trip to the washroom. Efforts by co-workers to scare off the bear were unsuccessful. The Royal Canadian Mounted Police shot and killed the bear upon arrival. A preliminary investigation determined that the attack was predatory.[15]",Black,0,0,0,1 +Adam Thomas Stewart,31,male,04/09/2014,9,2014,Wild,"Bridger-Teton National Forest, Wyoming","Stewart was conducting research alone in the Bridger-Teton National Forest in northwest Wyoming near the SE corner of Yellowstone National Park. This is high density bear habitat and he was in Cub Creek. After he failed to return, a search found his body.[32] The coroner suspects it was a grizzly bear, but the species hasn't officially been determined. The pathologist noted premortem punctures to Stewart's skull, indicating the cause of death was from a bear attack. The FWS report says he was not carrying bear spray or a firearm.[33]",Brown,0,1,0,1 +Rick Cross,54,male,07/09/2014,9,2014,Wild,"Kananaskis Country, Alberta","Cross, a hunter, was killed by a mother bear when he accidentally got between her and her cubs. Park rangers stated that it appeared that Cross managed to fire his rifle before being overwhelmed. RCMP said it appeared he wandered into the area where the mother and cub were feeding on a dead deer.[31]",Brown,1,0,0,1 +Ken Novotny,53,male,17/09/2014,9,2014,Wild,"near Norman Wells, Northwest Territories","While on a hunting trip near Norman Wells, Novotny was charged and struck by a bear. Friends reported Novotny had just killed a moose and was prepping his prize when the bear ""came out of nowhere."" He died on the scene. Authorities later found and killed the bear responsible for his death.[30]",Brown,1,0,0,1 +Darsh Patel,22,male,21/09/2014,9,2014,Wild,"near West Milford, New Jersey","Patel was about to begin hiking with four friends in Apshawa Preserve when they met a man and a woman at the entrance who told them there was a bear nearby and advised them to turn around.[12] They continued on, found the bear, and Patel and another hiker took photos. They turned and began walking away, but the bear followed them. The hikers ran in different directions, and found that Patel was missing when they regrouped. Authorities found Patel's body after searching for two hours. A black bear found in the vicinity was killed and a necropsy revealed human remains in its digestive tract.[13] According to the State Department of Environmental Protection, this was the first fatal bear attack on a human in New Jersey on record.[14]",Black,0,0,1,1 +Daniel Ward O'Connor,27,male,10/05/2015,5,2015,Wild,"near Mackenzie, British Columbia",Ward was killed by a bear while he slept near the fire pit at his campsite. His fiancée who slept in a nearby motorhome discovered his body the following morning. The bear was later shot and killed by conservation officers.[10][11],Black,0,0,0,1 +Lance Crosby,63,male,07/08/2015,8,2015,Wild,"Yellowstone National Park, Wyoming","Crosby, an employee at a medical clinic in the park, was reported missing when he did not report for work. A park ranger found his body in a popular off-trail area less than a mile (1 600 m) from Elephant Back Loop Trail, an area he was known to frequent. His body was partially consumed and covered. Puncture wounds on his arms indicated he had tried to defend himself. Based on the presence of a sow grizzly and a cub in the area, the sow was deemed responsible for the attack. The sow was captured and killed after it was found to be the bear that killed Crosby.[27][28] There were public appeals to not kill the sow, but the park superintendent decided there was a risk the sow might kill again; based on July 6, 2011 and August 24, 2011 killings in the park, where another sow was present at both those killings.[29]",Brown,0,1,0,1 +Brad Treat,38,male,29/06/2016,6,2016,Wild,"Flathead National Forest, Montana","Treat and another man were on mountain bikes on U.S. Forest Service land near Halfmoon Lakes. According to the official Board of Review report on the incident, Treat's mountain bike collided at high speed with a large male grizzly bear ""after rounding a blind curve in the trail."" The bear immediately attacked Treat in response to being struck by the bicycle. The second rider escaped uninjured and summoned help. The bear was identified via DNA from a previous research project, but was not captured or killed because its behavior was a natural response to a surprise encounter involving physical contact.[26]",Brown,0,1,0,1 +Patrick Cooper,16,male,18/06/2017,6,2017,Wild,"Indian, Alaska","Cooper was chased and mauled by a bear while running in the juniors' division of the Bird Ridge trail's running race. Cooper texted his family after completing the race, to say he was being followed by a bear. Searchers found the runner's remains 500 yards (457 m) from the trail and shot the bear in the face with a shotgun, which scared the bear and forced him into the woods away from the body.[9]",Black,0,0,0,1 +Erin Johnson,27,female,19/06/2017,6,2017,Wild,"Pogo mine, Alaska","Johnson, a contract employee for Pogo Mine, was killed while collecting soil samples. The bear was shot and killed by mine personnel.[7][8]",Black,0,0,0,1 +Mike Soltis,44,male,19/06/2018,6,2018,Wild,"Eagle River, Alaska","Soltis was backpacking alone along the Eagle River. After failing to return a search party was dispatched, rangers found a grizzly bear sitting on Soltis's remains. The bear then attacked the search party badly mauling one searcher. The search party retreated from the area. The bear escaped before more searchers arrived.[24][25]",Brown,0,1,0,1 +Aaron Gibbons,31,male,03/07/2018,7,2018,Wild,"Sentry Island, Nunavut","A polar bear approached a man and his children on Sentry Island. The man, identified as 31-year-old Aaron Gibbons from Arviat, put himself between the children and the bear and was attacked, causing fatal injuries. The bear was killed by other people who were also in the area.[52][53]",Polar,0,0,0,1 +Mark Uptain,37,male,14/09/2018,9,2018,Wild,"Teton Wilderness, Wyoming","Uptain, a guide for Martin Outfitters, was cleaning an elk that he and his client Corey Chubon had shot when the bear attacked. The bear was a sow with a 1½-year-old male cub.[22] As the bear attacked Uptain, Chubon attempted to throw him his Glock pistol but Uptain failed to catch it. Chubon fled with injuries as the bear attacked Uptain. After staggering 50 yards (46 m) uphill from the dead elk, Uptain was killed by the mother and possibly by the cub as well. The bears were shot and killed by Wyoming Fish and Game officials.[23]",Brown,0,0,0,1 +Anthony David Montoya,18,male,01/10/2018,10,2018,Wild,"Admiralty Island, Alaska","Montoya was working at a remote mining site on Admiralty Island, Alaska, when he was killed by a sow brown bear and two cubs. All three bears were killed.[21]",Brown,0,0,0,1 +Valérie Théorêt,37,female,26/11/2018,11,2018,Wild,"Einarson Lake, Yukon",The mother and child were attacked near their cabin while on a trip to manage trapping lines. The child's father Gjermund Roesholt shot the bear dead.[20],Brown,0,0,0,0 +Adele Roesholt,0.833333333,female,26/11/2018,11,2018,Wild,"Einarson Lake, Yukon",The mother and child were attacked near their cabin while on a trip to manage trapping lines. The child's father Gjermund Roesholt shot the bear dead.[20],Brown,0,0,0,0 diff --git a/class/3-quarto-plotting/figs/ggaes-1.png b/class/3-quarto-plotting/figs/ggaes-1.png new file mode 100644 index 0000000..18fe03c Binary files /dev/null and b/class/3-quarto-plotting/figs/ggaes-1.png differ diff --git a/class/3-quarto-plotting/figs/ggbar_p1-1.png b/class/3-quarto-plotting/figs/ggbar_p1-1.png new file mode 100644 index 0000000..893f16f Binary files /dev/null and b/class/3-quarto-plotting/figs/ggbar_p1-1.png differ diff --git a/class/3-quarto-plotting/figs/ggblank-1.png b/class/3-quarto-plotting/figs/ggblank-1.png new file mode 100644 index 0000000..9d3eb3e Binary files /dev/null and b/class/3-quarto-plotting/figs/ggblank-1.png differ diff --git a/class/3-quarto-plotting/figs/gglabs-1.png b/class/3-quarto-plotting/figs/gglabs-1.png new file mode 100644 index 0000000..08fa0db Binary files /dev/null and b/class/3-quarto-plotting/figs/gglabs-1.png differ diff --git a/class/3-quarto-plotting/figs/ggpoint-1.png b/class/3-quarto-plotting/figs/ggpoint-1.png new file mode 100644 index 0000000..e8539b9 Binary files /dev/null and b/class/3-quarto-plotting/figs/ggpoint-1.png differ diff --git a/class/3-quarto-plotting/figs/ggtheme_bw-1.png b/class/3-quarto-plotting/figs/ggtheme_bw-1.png new file mode 100644 index 0000000..f60b590 Binary files /dev/null and b/class/3-quarto-plotting/figs/ggtheme_bw-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-10-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-10-1.png new file mode 100644 index 0000000..04dcd06 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-10-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-12-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-12-1.png new file mode 100644 index 0000000..936aa12 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-12-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-13-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-13-1.png new file mode 100644 index 0000000..a1f4cf8 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-13-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-14-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-14-1.png new file mode 100644 index 0000000..a3a9594 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-14-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-15-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-15-1.png new file mode 100644 index 0000000..3508f65 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-15-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-16-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-16-1.png new file mode 100644 index 0000000..6dbdec8 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-16-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-17-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-17-1.png new file mode 100644 index 0000000..4524024 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-17-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-18-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-18-1.png new file mode 100644 index 0000000..bbf9049 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-18-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-19-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-19-1.png new file mode 100644 index 0000000..ef23b34 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-19-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-2-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-2-1.png new file mode 100644 index 0000000..e471d78 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-2-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-20-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-20-1.png new file mode 100644 index 0000000..6ba65ea Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-20-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-21-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-21-1.png new file mode 100644 index 0000000..97574eb Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-21-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-22-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-22-1.png new file mode 100644 index 0000000..0fe1d95 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-22-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-23-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-23-1.png new file mode 100644 index 0000000..b9ab9c0 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-23-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-24-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-24-1.png new file mode 100644 index 0000000..7b87d20 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-24-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-25-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-25-1.png new file mode 100644 index 0000000..8aacfeb Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-25-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-26-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-26-1.png new file mode 100644 index 0000000..8c8691b Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-26-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-27-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-27-1.png new file mode 100644 index 0000000..b041204 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-27-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-29-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-29-1.png new file mode 100644 index 0000000..55884f7 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-29-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-30-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-30-1.png new file mode 100644 index 0000000..65a19ea Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-30-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-32-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-32-1.png new file mode 100644 index 0000000..c587614 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-32-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-33-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-33-1.png new file mode 100644 index 0000000..940675d Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-33-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-34-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-34-1.png new file mode 100644 index 0000000..e3ccf8e Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-34-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-35-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-35-1.png new file mode 100644 index 0000000..248df6d Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-35-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-37-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-37-1.png new file mode 100644 index 0000000..ab390be Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-37-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-38-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-38-1.png new file mode 100644 index 0000000..d177746 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-38-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-4-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-4-1.png new file mode 100644 index 0000000..a8f67ab Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-4-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-5-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-5-1.png new file mode 100644 index 0000000..f88335a Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-5-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-6-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-6-1.png new file mode 100644 index 0000000..e2f4612 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-6-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-7-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-7-1.png new file mode 100644 index 0000000..2f52fab Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-7-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-8-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-8-1.png new file mode 100644 index 0000000..877779c Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-8-1.png differ diff --git a/class/3-quarto-plotting/figs/unnamed-chunk-9-1.png b/class/3-quarto-plotting/figs/unnamed-chunk-9-1.png new file mode 100644 index 0000000..7417998 Binary files /dev/null and b/class/3-quarto-plotting/figs/unnamed-chunk-9-1.png differ diff --git a/class/3-quarto-plotting/ggplot2.qmd b/class/3-quarto-plotting/ggplot2.qmd new file mode 100644 index 0000000..cc514d8 --- /dev/null +++ b/class/3-quarto-plotting/ggplot2.qmd @@ -0,0 +1,238 @@ +--- +title: "Practice with ggplot2" +format: + html: + toc: true + theme: flatly + self-contained: true +--- + +```{r} +#| label: setup +#| include: false + +knitr::opts_chunk$set( + warning = FALSE, + message = FALSE, + fig.path = "figs/", + fig.width = 7.252, + fig.height = 4, + comment = "#>", + fig.retina = 3 +) + +# Load libraries +library(tidyverse) +library(here) + +# Read in data +bears <- read_csv(here::here('data', 'bear_killings.csv')) +``` + +### Where should I put the `aes()` bit? + +If you put it at the "top level" inside `ggplot(aes(...))`, the mapping will apply to all levels. For example: + +```{r, fig.height=4, fig.width=6} +bears %>% + count(month) %>% + ggplot(aes(x = month, y = n)) + + geom_point() + + geom_line() +``` + +In contrast, if you put the `aes()` mapping inside a single geometry layer, it will only apply to that layer. For example, this will cause an error since the `geom_line()` part doesn't have an aesthetic mapping: + +```{r, fig.height=4, fig.width=6, error=TRUE} +bears %>% + count(month) %>% + ggplot() + + geom_point(aes(x = month, y = n)) + + geom_line() +``` + +# Main geoms + +## `geom_point()` + +Basic scatterplot: + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot() + + geom_point(aes(x = displ, y = hwy)) +``` + +Change color for all points: + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot() + + geom_point(aes(x = displ, y = hwy), color = 'blue') +``` + +To change color based on a variable, map the variable to `color` in `aes()`: + +```{r, fig.height=4, fig.width=7} +mpg %>% + ggplot() + + geom_point(aes(x = displ, y = hwy, color = class)) +``` + +Map the shape instead of color (usually not a great idea): + +```{r, fig.height=4, fig.width=7} +mpg %>% + ggplot() + + geom_point(aes(x = displ, y = hwy, shape = class)) +``` + +What happened to SUV? + +## `geom_line()` vs. `geom_smooth()` + +`geom_line()` connects all the dots: + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot() + + geom_line(aes(x = displ, y = hwy)) +``` + +The reason this looks messy is because `geom_line()` is trying to literally connect every dot from left to right. + +If you wanted a single "best-fit" trend line, use `geom_smooth()`: + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot() + + geom_smooth(aes(x = displ, y = hwy)) +``` + +Set `se = FALSE` to drop the error bounds: + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot() + + geom_smooth(aes(x = displ, y = hwy), se = FALSE) +``` + +## `geom_col()` + +For these examples, I'm creating a smaller summary data frame first that just counts how many rows there are for each class: + +```{r} +mpg %>% + count(class) +``` + +Basic bar plot of the counts: + +```{r, fig.height=4, fig.width=7} +mpg %>% + count(class) %>% + ggplot() + + geom_col(aes(x = class, y = n), width = 0.7) # width is width of bars +``` + +Re-order bars based on count using `reorder()`: + +```{r, fig.height=4, fig.width=7} +mpg %>% + count(class) %>% + ggplot() + + geom_col(aes(x = reorder(class, n), y = n), width = 0.7) +``` + +To change the color for all bars, use `fill` (not `color`): + +```{r, fig.height=4, fig.width=7} +mpg %>% + count(class) %>% + ggplot() + + geom_col(aes(x = reorder(class, n), y = n), fill = 'blue', width = 0.7) +``` + +To change color based on a variable, map the variable to `fill` in `aes()`: + +```{r, fig.height=4, fig.width=7} +mpg %>% + count(class, drv) %>% # Note I had to include drv in the count too + ggplot() + + geom_col(aes(x = reorder(class, n), y = n, fill = drv), width = 0.7) +``` + +Use `position = 'dodge'` to change from stacked to side-by-side: + +```{r, fig.height=4, fig.width=8} +mpg %>% + count(class, drv) %>% # Note I had to include drv in the count too + ggplot() + + geom_col( + aes(x = reorder(class, n), y = n, fill = drv), + position = "dodge", width = 0.7) +``` + +# Practice + +```{r, fig.height=4, fig.width=7} + +``` + + +```{r, fig.height=4, fig.width=6} + +``` + + +```{r, fig.height=4, fig.width=6} + +``` + + + +# Facets + +Facets make multiple small charts and are useful when you have many levels in a categorical variable. + +For example, this plot has too many color categories for the color to be useful: + +```{r, fig.height=4, fig.width=7} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point(aes(color = class)) +``` + +Instead, we can use `facet_wrap()` to show multiple charts of each vehicle class: + +```{r, fig.height=9, fig.width=10} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point() + + facet_wrap(~class) +``` + +You can also use `facet_grid()` to facet by two variables: + +```{r, fig.height=7, fig.width=10} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point() + + facet_grid(drv ~ cyl) +``` + + + +# Extra Practice + +```{r, fig.height=5, fig.width=7} +bears %>% + count(year, gender) +``` + +```{r, fig.height=4, fig.width=6} +mpg %>% + mutate(manufacturer = str_to_title(manufacturer)) %>% + group_by(manufacturer) %>% + summarise(mean_hwy = mean(hwy)) +``` diff --git a/class/3-quarto-plotting/ggplot2_solutions.qmd b/class/3-quarto-plotting/ggplot2_solutions.qmd new file mode 100644 index 0000000..033bc70 --- /dev/null +++ b/class/3-quarto-plotting/ggplot2_solutions.qmd @@ -0,0 +1,261 @@ +--- +title: "Practice with ggplot2" +format: + html: + toc: true + theme: flatly + self-contained: true +--- + +```{r} +#| label: setup +#| include: false + +knitr::opts_chunk$set( + warning = FALSE, + message = FALSE, + fig.path = "figs/", + fig.width = 7.252, + fig.height = 4, + comment = "#>", + fig.retina = 3 +) + +# Load libraries +library(tidyverse) +library(here) + +# Read in data +bears <- read_csv(here::here('data', 'bear_killings.csv')) +``` + +### Where should I put the `aes()` bit? + +If you put it at the "top level" inside `ggplot(aes(...))`, the mapping will apply to all levels. For example: + +```{r, fig.height=4, fig.width=6} +bears %>% + count(month) %>% + ggplot(aes(x = month, y = n)) + + geom_point() + + geom_line() +``` + +In contrast, if you put the `aes()` mapping inside a single geometry layer, it will only apply to that layer. For example, this will cause an error since the `geom_line()` part doesn't have an aesthetic mapping: + +```{r, fig.height=4, fig.width=6, error=TRUE} +bears %>% + count(month) %>% + ggplot() + + geom_point(aes(x = month, y = n)) + + geom_line() +``` + +# Main geoms + +## `geom_point()` + +Basic scatterplot: + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot() + + geom_point(aes(x = displ, y = hwy)) +``` + +Change color for all points: + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot() + + geom_point(aes(x = displ, y = hwy), color = 'blue') +``` + +To change color based on a variable, map the variable to `color` in `aes()`: + +```{r, fig.height=4, fig.width=7} +mpg %>% + ggplot() + + geom_point(aes(x = displ, y = hwy, color = class)) +``` + +Map the shape instead of color (usually not a great idea): + +```{r, fig.height=4, fig.width=7} +mpg %>% + ggplot() + + geom_point(aes(x = displ, y = hwy, shape = class)) +``` + +What happened to SUV? + +## `geom_line()` vs. `geom_smooth()` + +`geom_line()` connects all the dots: + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot() + + geom_line(aes(x = displ, y = hwy)) +``` + +The reason this looks messy is because `geom_line()` is trying to literally connect every dot from left to right. + +If you wanted a single "best-fit" trend line, use `geom_smooth()`: + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot() + + geom_smooth(aes(x = displ, y = hwy)) +``` + +Set `se = FALSE` to drop the error bounds: + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot() + + geom_smooth(aes(x = displ, y = hwy), se = FALSE) +``` + +## `geom_col()` + +For these examples, I'm creating a smaller summary data frame first that just counts how many rows there are for each class: + +```{r} +mpg %>% + count(class) +``` + +Basic bar plot of the counts: + +```{r, fig.height=4, fig.width=7} +mpg %>% + count(class) %>% + ggplot() + + geom_col(aes(x = class, y = n), width = 0.7) # width is width of bars +``` + +Re-order bars based on count using `reorder()`: + +```{r, fig.height=4, fig.width=7} +mpg %>% + count(class) %>% + ggplot() + + geom_col(aes(x = reorder(class, n), y = n), width = 0.7) +``` + +To change the color for all bars, use `fill` (not `color`): + +```{r, fig.height=4, fig.width=7} +mpg %>% + count(class) %>% + ggplot() + + geom_col(aes(x = reorder(class, n), y = n), fill = 'blue', width = 0.7) +``` + +To change color based on a variable, map the variable to `fill` in `aes()`: + +```{r, fig.height=4, fig.width=7} +mpg %>% + count(class, drv) %>% # Note I had to include drv in the count too + ggplot() + + geom_col(aes(x = reorder(class, n), y = n, fill = drv), width = 0.7) +``` + +Use `position = 'dodge'` to change from stacked to side-by-side: + +```{r, fig.height=4, fig.width=8} +mpg %>% + count(class, drv) %>% # Note I had to include drv in the count too + ggplot() + + geom_col( + aes(x = reorder(class, n), y = n, fill = drv), + position = "dodge", width = 0.7) +``` + +# Practice + +```{r, fig.height=4, fig.width=7} +mpg %>% + ggplot() + + geom_smooth(aes(x = displ, y = hwy, color = drv)) +``` + +```{r, fig.height=4, fig.width=6} +mpg %>% + count(class, drv) %>% + ggplot() + + geom_col(aes(x = drv, y = n, fill = class), width = 0.7) +``` + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point(aes(color = class)) + + geom_smooth(se = FALSE) +``` + + + +# Facets + +Facets make multiple small charts and are useful when you have many levels in a categorical variable. + +For example, this plot has too many color categories for the color to be useful: + +```{r, fig.height=4, fig.width=7} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point(aes(color = class)) +``` + +Instead, we can use `facet_wrap()` to show multiple charts of each vehicle class: + +```{r, fig.height=9, fig.width=10} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point() + + facet_wrap(~class) +``` + +You can also use `facet_grid()` to facet by two variables: + +```{r, fig.height=7, fig.width=10} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point() + + facet_grid(drv ~ cyl) +``` + + + +# Extra Practice + +```{r, fig.height=5, fig.width=7} +bears %>% + count(year, gender) %>% + ggplot() + + geom_col(aes(x = year, y = n, fill = gender)) + + labs( + x = "Year", + y = 'Number of killings', + fill = "Victim gender", + title = "Annual deadly bear attacks over time" + ) + + theme_bw() +``` + +```{r, fig.height=4, fig.width=6} +mpg %>% + mutate(manufacturer = str_to_title(manufacturer)) %>% + group_by(manufacturer) %>% + summarise(mean_hwy = mean(hwy)) %>% + ggplot() + + geom_col(aes(x = mean_hwy, y = reorder(manufacturer, mean_hwy)), width = 0.9) + + labs( + x = 'Highway fuel economy (mpg)', + y = 'Vehicle manufacturer', + title = 'Mean fuel economy by automaker' + ) + + theme_minimal() +``` diff --git a/class/3-quarto-plotting/images/Knuth.jpg b/class/3-quarto-plotting/images/Knuth.jpg new file mode 100644 index 0000000..9ad890c Binary files /dev/null and b/class/3-quarto-plotting/images/Knuth.jpg differ diff --git a/class/3-quarto-plotting/images/chunks_options.png b/class/3-quarto-plotting/images/chunks_options.png new file mode 100644 index 0000000..b47c77b Binary files /dev/null and b/class/3-quarto-plotting/images/chunks_options.png differ diff --git a/class/3-quarto-plotting/images/horst_monsters_rmarkdown.png b/class/3-quarto-plotting/images/horst_monsters_rmarkdown.png new file mode 100644 index 0000000..a7115ae Binary files /dev/null and b/class/3-quarto-plotting/images/horst_monsters_rmarkdown.png differ diff --git a/class/3-quarto-plotting/images/how-qmd-works.png b/class/3-quarto-plotting/images/how-qmd-works.png new file mode 100644 index 0000000..45b67f5 Binary files /dev/null and b/class/3-quarto-plotting/images/how-qmd-works.png differ diff --git a/class/3-quarto-plotting/images/knit-dropdown.png b/class/3-quarto-plotting/images/knit-dropdown.png new file mode 100644 index 0000000..48b2136 Binary files /dev/null and b/class/3-quarto-plotting/images/knit-dropdown.png differ diff --git a/class/3-quarto-plotting/images/making_a_ggplot.jpeg b/class/3-quarto-plotting/images/making_a_ggplot.jpeg new file mode 100644 index 0000000..be13e04 Binary files /dev/null and b/class/3-quarto-plotting/images/making_a_ggplot.jpeg differ diff --git a/class/3-quarto-plotting/images/solar2.png b/class/3-quarto-plotting/images/solar2.png new file mode 100644 index 0000000..9308136 Binary files /dev/null and b/class/3-quarto-plotting/images/solar2.png differ diff --git a/class/3-quarto-plotting/images/visual-mode.png b/class/3-quarto-plotting/images/visual-mode.png new file mode 100644 index 0000000..3a5fb26 Binary files /dev/null and b/class/3-quarto-plotting/images/visual-mode.png differ diff --git a/class/3-quarto-plotting/index.Rmd b/class/3-quarto-plotting/index.Rmd new file mode 100644 index 0000000..d135caa --- /dev/null +++ b/class/3-quarto-plotting/index.Rmd @@ -0,0 +1,1078 @@ +--- +title: "Quarto & Plotting" +subtitle: "EMSE 6035: Marketing Analytics for Design Decisions" +date: September 13, 2023 +week: 3 +author: "John Paul Helveston" +institute: "The George Washington University | Dept. of Engineering Management and Systems Engineering" +output: + xaringan::moon_reader: + css: + - default + - css/lexis.css + - css/lexis-fonts.css + lib_dir: libs + seal: false + nature: + highlightStyle: github + highlightLines: true + countIncrementalSlides: false + ratio: "16:9" +--- + +```{r setup, child="../setup.Rmd"} +``` + +```{r} +#| include: false + +# For this class +data <- mtcars +bears <- read_csv(here::here('data', 'bear_killings.csv')) +theme_set(theme_gray(base_size = 16)) +``` + +--- + +```{r child="topics/0.Rmd"} +``` + +--- + +```{r child="topics/1.Rmd"} +``` + +--- + +class: center + +# "Literate programming" + +.leftcol[.left[ + +> ### Treat programs as a "literature" understandable to **human beings** + +]] + +.rightcol[.center[ + +
+ +
+ +[Donald E. Knuth](https://en.wikipedia.org/wiki/Donald_Knuth) + +]] + +--- + + +class: middle, inverse + +# .center[Quick demo] + +
+ +# 1. Open `quarto_demo.qmd` + +# 2. Click "Render" + +
+ +
+ +--- + +# .center[Anatomy of a .qmd file] + +
+ +# .red[Header] + +# Markdown text + +# R code + +--- + +# Define overall document options in header + +.leftcol[ + +Basic html page + +``` +--- +title: Your title +author: Author name +format: html +--- +``` + +] + +.rightcol[ + +Add table of contents, change theme + +``` +--- +title: Your title +author: Author name +toc: true +format: + html: + theme: united +--- +``` + +More on themes at https://quarto.org/docs/output-formats/html-themes.html + +] + +--- + +# Render to multiple outputs + +.leftcol[ + +### PDF uses LaTeX + +``` +--- +title: Your title +author: Author name +format: pdf +--- +``` + +If you don't have LaTeX on your computer, install tinytex in R: + +```{r} +#| eval: false + +tinytex::install_tinytex() +``` + +] + +.rightcol[ + +### Microsoft Word + +``` +--- +title: Your title +author: Author name +format: docx +--- +``` + +] + +--- + +# .center[Anatomy of a .qmd file] + +
+ +# ~~Header~~ + +# .red[Markdown text] + +# R code + +--- + +class: center + +# Right now, bookmark this! `r emo::ji("point_down")` + +# https://commonmark.org/help/ + +


+ +# (When you have 10 minutes, do this! `r emo::ji("point_down")`) + +# https://commonmark.org/help/tutorial/ + +--- + +# .center[Headers] + +-- + +.leftcol[ + +```markdown +# HEADER 1 + +## HEADER 2 + +### HEADER 3 + +#### HEADER 4 + +##### HEADER 5 + +###### HEADER 6 +``` + +] + +-- + +.rightcol[ + +# HEADER 1 + +## HEADER 2 + +### HEADER 3 + +#### HEADER 4 + +##### HEADER 5 + +###### HEADER 6 + +] + +--- + +# .center[Basic Text Formatting] + +.leftcol[ + +## Type this... + +- `normal text` +- `_italic text_` +- `*italic text*` +- `**bold text**` +- `***bold italic text***` +- `~~strikethrough~~` +- `` `code text` `` + +] + +.rightcol[ + +## ..to get this + +- normal text +- _italic text_ +- *italic text* +- **bold text** +- ***bold italic text*** +- ~~strikethrough~~ +- `code text` + +] + +--- + +class: top + +# .center[Lists] + +.leftcol[ + +Bullet list: + +```{r, eval=FALSE} +- first item +- second item +- third item +``` + +- first item +- second item +- third item + +] + +.rightcol[ + +Numbered list: + +```{r, eval=FALSE} +1. first item +2. second item +3. third item +``` + +1. first item +2. second item +3. third item + +] + +--- + +# .center[Links] + +Simple **url link** to another site: + +```{r, eval=FALSE} +[Download R](http://www.r-project.org/) +``` + +[Download R](http://www.r-project.org/) + +--- + +class: middle, center + +# Don't want to use Markdown? + +# .red[Use Visual Mode!] + +
+ +
+ +--- + +# .center[Anatomy of a .qmd file] + +
+ +# ~~Header (think of this as the "settings")~~ + +# ~~Markdown text~~ + +# .red[R code] + +--- + +class: center + +# R Code + +-- + +.leftcol[ + +## Inline code + +.left[ +```{r, eval=FALSE} +`r insert code here` +``` + +]] + +-- + +.rightcol[ + +## Code chunks + +.left[ + +````markdown +`r ''````{r} +insert code here +insert more code here +``` +```` + +]] + +--- + +# Inline R code + +```{r, eval=FALSE} +The sum of 3 and 4 is `r 3 + 4` +``` + +-- + +Produces this: + +The sum of 3 and 4 is `r 3 + 4` + +--- + +# R Code chunks + +.leftcol[ + +This code chunk... + +````markdown +`r ''````{r} +library(palmerpenguins) + +head(penguins) +``` +```` + +] + +-- + +.rightcol[ + +...will produce this when compiled: + +```{r} +library(palmerpenguins) + +head(penguins) +``` + +] + +--- + +# Chunk options + +Control what chunks output using options + +All options [here](https://quarto.org/docs/reference/cells/cells-knitr.html) + +```{r, echo=FALSE, out.width='60%'} +knitr::include_graphics('images/chunks_options.png') +``` + +--- + +# .center[Chunk output options] + +.center[By default, code chunks print **code** + **output**] + +-- + +.cols3[ + +````markdown +`r ''````{r} +#| echo: false + +cat('hello world!') +``` +```` + +Prints only **output**
(doesn't show code) + +```{r} +#| echo: false + +cat('hello world!') +``` + +] + +-- + +.cols3[ + +````markdown +`r ''````{r} +#| eval: false + +cat('hello world!') +``` +```` + +Prints only **code**
(doesn't run the code) + +```{r} +#| eval: false + +cat('hello world!') +``` + +] + +-- + +.cols3[ + +````markdown +`r ''````{r} +#| include: false + +cat('hello world!') +``` +```` + +Runs, but doesn't print anything + +```{r} +#| include: false + +cat('hello world!') +``` + +] + +--- + +# message / warning + +![](https://www.tidyverse.org/images/tidyverse_1.2.0/tidyverse_1-2-0_pkg_load.gif) + +--- + +# message / warning + +Drop messages and warnings in chunk settings + +.leftcol[ + +````markdown +`r ''````{r} +#| message: false +#| warning: false + +library(tidyverse) +``` +```` + +] + +--- + +# A global `setup` chunk 🌍 + +.leftcol[ + +````markdown +`r ''````{r} +#| label: setup +#| include: false + +knitr::opts_chunk$set( + warning = FALSE, + message = FALSE, + fig.path = "figs/", + fig.width = 7.252, + fig.height = 4, + comment = "#>", + fig.retina = 3 +) +``` +```` + +] + +.rightcol[ + +- Typically the first chunk +- All following chunks will use these options (i.e., sets global chunk options) +- You can (and should) use individual chunk options too +- Often where I load libraries, etc. + +] + +--- + +class: inverse + +```{r, echo=FALSE} +countdown( + minutes = 15, + warn_when = 15, + update_every = 1, + top = 0, + right = 0, + font_size = '2em' +) +``` + +# Your turn + +.font90[ + +1) Open the `bears.qmd` file, and title it _"Bears Analysis"_ + +2) Create a "setup" code chunk to read in the `bear_killings.csv` data file
(HINT: You might want to look back at the `quarto_demo.qmd` file!). + +3) Use text and code to find answers each of the following questions - show your code and results to justify each answer: + +- Which months have the highest frequency of bear killings? +- Who has been killed more often by bears: hunters or hikers? +- How do the the number of bear attacks on men vs women compare? + +HINT: Use `bears %>% count(variable)` to count how many rows are in the data for each unique value of `variable` + +] + +--- + +class: inverse + +# Quiz 1 + +```{r, echo=FALSE} +countdown( + minutes = 10, + warn_when = 30, + update_every = 1, + bottom = 0, + left = 0, + font_size = '4em' +) +``` + +.leftcol[ + +### Download the template from the #class channel + +### Make sure you unzip it! + +### When done, submit your `quiz1.qmd` on Blackboard + +] + +.rightcol[ + +
+ +
+ +] + +--- + +```{r child="topics/2.Rmd"} +``` + +--- + +.leftcol[ + + + +] + +.rightcol[ + +# "Grammar of Graphics" + +Concept developed by Leland Wilkinson (1999) + +**ggplot2** package developed by Hadley Wickham (2005) + +] + +--- + +# Making plot layers with ggplot2 + +
+ +### 1. The data +### 2. The aesthetic mapping (what goes on the axes?) +### 3. The geometries (points? bars? etc.) +### 4. The annotations / labels +### 5. The theme + +--- + +# Layer 1: The data + +```{r} +head(mpg) +``` + +--- + +# Layer 1: The data + +The `ggplot()` function initializes the plot with whatever data you're using + +.leftcol[ + +```{r ggblank, fig.show='hide'} +mpg %>% + ggplot() +``` + +] + +.rightcol[.blackborder[ + +```{r ref.label='ggblank', echo=FALSE, fig.height=5, fig.width=7} +``` + +]] + +--- + +# Layer 2: The aesthetic mapping + +The `aes()` function determines which variables will be _mapped_ to the geometries
(e.g. the axes) + +.leftcol[ + +```{r ggaes, fig.show='hide'} +mpg %>% + ggplot(aes(x = displ, y = hwy)) #<< +``` + +] + +.rightcol[.blackborder[ + +```{r ref.label='ggaes', echo=FALSE, fig.height=5, fig.width=7} +``` + +]] + +--- + +# Layer 3: The geometries + +Use `+` to add geometries, e.g. `geom_points()` for points + +.leftcol[ + +```{r ggpoint, fig.show='hide'} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point() #<< +``` + +] + +.rightcol[.blackborder[ + +```{r ref.label='ggpoint', echo=FALSE, fig.height=5, fig.width=7} +``` + +]] + +--- + +# Layer 4: The annotations / labels + +Use `labs()` to modify most labels + +.leftcol[ + +```{r gglabs, fig.show='hide'} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point() + + labs( #<< + x = "Engine displacement (liters)", #<< + y = "Highway fuel economy (mpg)", #<< + title = "Most larger engine vehicles are less fuel efficient" #<< + ) #<< +``` + +] + +.rightcol[ + +```{r ref.label='gglabs', echo=FALSE, fig.height=5, fig.width=7} +``` + +] + +--- + +# Layer 5: The theme + +.leftcol[ + +```{r ggtheme_bw, fig.show='hide'} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point() + + labs( + x = "Engine displacement (liters)", + y = "Highway fuel economy (mpg)", + title = "Most larger engine vehicles are less fuel efficient" + ) + + theme_bw() #<< +``` + +] + + +.rightcol[ + +```{r ref.label='ggtheme_bw', echo=FALSE, fig.height=5, fig.width=7} +``` + +] + +--- + +### Common themes + +.leftcol[ + +`theme_bw()` + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point() + + theme_bw() #<< +``` + +] + +.rightcol[ + +`theme_minimal()` + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point() + + theme_minimal() #<< +``` + +] + +--- + +### Common themes + +.leftcol[ + +`theme_classic()` + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point() + + theme_classic() #<< +``` + +] + +.rightcol[ + +`theme_void()` + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point() + + theme_void() #<< +``` + +] + +--- + +### Other themes: [hrbrthemes](https://github.com/hrbrmstr/hrbrthemes) + +```{r, eval=FALSE} +remotes::install_github("hrbrmstr/hrbrthemes") +``` + +.leftcol[ + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point() + + hrbrthemes::theme_ipsum() #<< +``` + +] + +.rightcol[ + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point() + + hrbrthemes::theme_ft_rc() #<< +``` + +] + +--- + +### Other themes: [ggthemes](https://jrnold.github.io/ggthemes/) + +```{r, eval=FALSE} +install.packages('ggthemes', dependencies = TRUE) +``` + +.leftcol[ + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point() + + ggthemes::theme_economist() #<< +``` + +] + +.rightcol[ + +```{r, fig.height=4, fig.width=6} +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point() + + ggthemes::theme_economist_white() #<< +``` + +] + +--- + +class: center, middle, inverse + +# More practice + +# Open `ggplot2.qmd` + +--- + +class: middle, inverse + +.leftcol[ + +```{r} +#| echo: false + +mpg %>% + ggplot() + + geom_smooth(aes(x = displ, y = hwy, color = drv)) +``` + +```{r} +#| echo: false + +mpg %>% + count(class, drv) %>% + ggplot() + + geom_col(aes(x = drv, y = n, fill = class), width = 0.7) +``` + +] + +.rightcol[ + +```{r, echo=FALSE} +countdown( + minutes = 15, + warn_when = 15, + update_every = 1, + top = 0, + right = 0, + font_size = '2em' +) +``` + +## Your turn + +Open `practice.qmd` + +Use the `mpg` data frame and ggplot to create these charts + +```{r} +#| echo: false + +mpg %>% + ggplot(aes(x = displ, y = hwy)) + + geom_point(aes(color = class)) + + geom_smooth(se = FALSE) +``` + +] + +--- + +class: inverse + +# Extra practice + +.leftcol[ + +```{r, ggbar_p1, echo=FALSE, fig.height=5, fig.width=7} +bears %>% + count(year, gender) %>% + ggplot() + + geom_col(aes(x = year, y = n, fill = gender)) + + labs( + x = "Year", + y = 'Number of killings', + fill = "Victim gender", + title = "Annual deadly bear attacks over time" + ) + + theme_bw() +``` + +] + +.rightcol[ + +```{r, fig.height=4, fig.width=6, echo=FALSE} +mpg %>% + mutate(manufacturer = str_to_title(manufacturer)) %>% + group_by(manufacturer) %>% + summarise(mean_hwy = mean(hwy)) %>% + ggplot() + + geom_col( + aes(x = mean_hwy, y = reorder(manufacturer, mean_hwy)), + width = 0.9) + + labs( + x = 'Highway fuel economy (mpg)', + y = 'Vehicle manufacturer', + title = 'Mean fuel economy by automaker' + ) + + theme_minimal() +``` + +] + +--- + +```{r child="topics/3.Rmd"} +``` + +--- + +class: center + +# Model Relationships Table ([example](https://docs.google.com/spreadsheets/d/1Hmxfav_l1bubnaPkIiiMW0tZrFA-xblP9_ndN_6TB1I/edit?usp=sharing)) + +.border[ + +
+ +
+ +] + +## Start defining attribute _levels_ + +--- + +# Defining attribute levels + +.leftcol[ + +## Continuous + +- **Price**: 1, 2, 3, 4, 5 ($) +- **Power Output**: 60, 80, 120 (Watts) + +## Discrete + +- **Color**: Red, Blue, Yellow +- **Material**: Plastic, Aluminum, Glass + +] + +.rightcol[ + +- Look at competitors +- Search web for values that cover the full set of values available today (and maybe some into the future) + +] diff --git a/class/3-quarto-plotting/index.html b/class/3-quarto-plotting/index.html new file mode 100644 index 0000000..d6f8c5a --- /dev/null +++ b/class/3-quarto-plotting/index.html @@ -0,0 +1,1269 @@ + + + + Quarto & Plotting + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/class/3-quarto-plotting/libs/clipboard/clipboard.min.js b/class/3-quarto-plotting/libs/clipboard/clipboard.min.js new file mode 100644 index 0000000..28650f3 --- /dev/null +++ b/class/3-quarto-plotting/libs/clipboard/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.6 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return o={},r.m=n=[function(t,e){t.exports=function(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){var o=this;function r(){o.off(t,r),e.apply(n,arguments)}return r._=e,this.on(t,r,n)},emit:function(t){for(var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;o button { + font-size: 1.5rem; + width: 1rem; + height: 1rem; + display: inline-block; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-family: monospace; + padding: 10px; + margin: 0; + background: inherit; + border: 2px solid; + border-radius: 100%; + transition: 50ms transform ease-in-out, 150ms opacity ease-in; + --countdown-transition-distance: 10px; +} + +.countdown .countdown-controls > button:last-child { + transform: translate(calc(-1 * var(--countdown-transition-distance)), var(--countdown-transition-distance)); + opacity: 0; + color: #002F14FF; + background-color: #43AC6A; + border-color: #2A9B59FF; +} + +.countdown .countdown-controls > button:first-child { + transform: translate(var(--countdown-transition-distance), var(--countdown-transition-distance)); + opacity: 0; + color: #4A0900FF; + background-color: #F04124; + border-color: #DE3000FF; +} + +.countdown.running:hover .countdown-controls > button, +.countdown.running:focus-within .countdown-controls > button{ + transform: translate(0, 0); + opacity: 1; +} + +.countdown.running:hover .countdown-controls > button:hover, +.countdown.running:focus-within .countdown-controls > button:hover{ + transform: translate(0, calc(var(--countdown-transition-distance) / -2)); + box-shadow: 0px 2px 5px 0px rgba(50, 50, 50, 0.4); + -webkit-box-shadow: 0px 2px 5px 0px rgba(50, 50, 50, 0.4); +} + +.countdown.running:hover .countdown-controls > button:active, +.countdown.running:focus-within .countdown-controls > button:active{ + transform: translate(0, calc(var(--coutndown-transition-distance) / -5)); +} + +/* ----- Fullscreen ----- */ +.countdown.countdown-fullscreen { + z-index: 0; +} + +.countdown-fullscreen.running .countdown-controls { + top: 1rem; + left: 0; + right: 0; + justify-content: center; +} + +.countdown-fullscreen.running .countdown-controls > button + button { + margin-left: 1rem; +} diff --git a/class/3-quarto-plotting/libs/countdown-0.4.0/countdown.js b/class/3-quarto-plotting/libs/countdown-0.4.0/countdown.js new file mode 100644 index 0000000..a058ad8 --- /dev/null +++ b/class/3-quarto-plotting/libs/countdown-0.4.0/countdown.js @@ -0,0 +1,478 @@ +/* globals Shiny,Audio */ +class CountdownTimer { + constructor (el, opts) { + if (typeof el === 'string' || el instanceof String) { + el = document.querySelector(el) + } + + if (el.counter) { + return el.counter + } + + const minutes = parseInt(el.querySelector('.minutes').innerText || '0') + const seconds = parseInt(el.querySelector('.seconds').innerText || '0') + const duration = minutes * 60 + seconds + + function attrIsTrue (x) { + if (x === true) return true + return !!(x === 'true' || x === '' || x === '1') + } + + this.element = el + this.duration = duration + this.end = null + this.is_running = false + this.warn_when = parseInt(el.dataset.warnWhen) || -1 + this.update_every = parseInt(el.dataset.updateEvery) || 1 + this.play_sound = attrIsTrue(el.dataset.playSound) + this.blink_colon = attrIsTrue(el.dataset.blinkColon) + this.startImmediately = attrIsTrue(el.dataset.startImmediately) + this.timeout = null + this.display = { minutes, seconds } + + if (opts.src_location) { + this.src_location = opts.src_location + } + + this.addEventListeners() + } + + addEventListeners () { + const self = this + + if (this.startImmediately) { + if (window.remark && window.slideshow) { + // Remark (xaringan) support + const isOnVisibleSlide = () => { + return document.querySelector('.remark-visible').contains(self.element) + } + if (isOnVisibleSlide()) { + self.start() + } else { + let started_once = 0 + window.slideshow.on('afterShowSlide', function () { + if (started_once > 0) return + if (isOnVisibleSlide()) { + self.start() + started_once = 1 + } + }) + } + } else if (window.Reveal) { + // Revealjs (quarto) support + const isOnVisibleSlide = () => { + const currentSlide = document.querySelector('.reveal .slide.present') + return currentSlide ? currentSlide.contains(self.element) : false + } + if (isOnVisibleSlide()) { + self.start() + } else { + const revealStartTimer = () => { + if (isOnVisibleSlide()) { + self.start() + window.Reveal.off('slidechanged', revealStartTimer) + } + } + window.Reveal.on('slidechanged', revealStartTimer) + } + } else if (window.IntersectionObserver) { + // All other situtations use IntersectionObserver + const onVisible = (element, callback) => { + new window.IntersectionObserver((entries, observer) => { + entries.forEach(entry => { + if (entry.intersectionRatio > 0) { + callback(element) + observer.disconnect() + } + }) + }).observe(element) + } + onVisible(this.element, el => el.countdown.start()) + } else { + // or just start the timer as soon as it's initialized + this.start() + } + } + + function haltEvent (ev) { + ev.preventDefault() + ev.stopPropagation() + } + function isSpaceOrEnter (ev) { + return ev.code === 'Space' || ev.code === 'Enter' + } + function isArrowUpOrDown (ev) { + return ev.code === 'ArrowUp' || ev.code === 'ArrowDown' + } + + ;['click', 'touchend'].forEach(function (eventType) { + self.element.addEventListener(eventType, function (ev) { + haltEvent(ev) + self.is_running ? self.stop() : self.start() + }) + }) + this.element.addEventListener('keydown', function (ev) { + if (ev.code === "Escape") { + self.reset() + haltEvent(ev) + } + if (!isSpaceOrEnter(ev) && !isArrowUpOrDown(ev)) return + haltEvent(ev) + if (isSpaceOrEnter(ev)) { + self.is_running ? self.stop() : self.start() + return + } + + if (!self.is_running) return + + if (ev.code === 'ArrowUp') { + self.bumpUp() + } else if (ev.code === 'ArrowDown') { + self.bumpDown() + } + }) + this.element.addEventListener('dblclick', function (ev) { + haltEvent(ev) + if (self.is_running) self.reset() + }) + this.element.addEventListener('touchmove', haltEvent) + + const btnBumpDown = this.element.querySelector('.countdown-bump-down') + ;['click', 'touchend'].forEach(function (eventType) { + btnBumpDown.addEventListener(eventType, function (ev) { + haltEvent(ev) + if (self.is_running) self.bumpDown() + }) + }) + btnBumpDown.addEventListener('keydown', function (ev) { + if (!isSpaceOrEnter(ev) || !self.is_running) return + haltEvent(ev) + self.bumpDown() + }) + + const btnBumpUp = this.element.querySelector('.countdown-bump-up') + ;['click', 'touchend'].forEach(function (eventType) { + btnBumpUp.addEventListener(eventType, function (ev) { + haltEvent(ev) + if (self.is_running) self.bumpUp() + }) + }) + btnBumpUp.addEventListener('keydown', function (ev) { + if (!isSpaceOrEnter(ev) || !self.is_running) return + haltEvent(ev) + self.bumpUp() + }) + this.element.querySelector('.countdown-controls').addEventListener('dblclick', function (ev) { + haltEvent(ev) + }) + } + + remainingTime () { + const remaining = this.is_running + ? (this.end - Date.now()) / 1000 + : this.remaining || this.duration + + let minutes = Math.floor(remaining / 60) + let seconds = Math.ceil(remaining - minutes * 60) + + if (seconds > 59) { + minutes = minutes + 1 + seconds = seconds - 60 + } + + return { remaining, minutes, seconds } + } + + start () { + if (this.is_running) return + + this.is_running = true + + if (this.remaining) { + // Having a static remaining time indicates timer was paused + this.end = Date.now() + this.remaining * 1000 + this.remaining = null + } else { + this.end = Date.now() + this.duration * 1000 + } + + this.reportStateToShiny('start') + + this.element.classList.remove('finished') + this.element.classList.add('running') + this.update(true) + this.tick() + } + + tick (run_again) { + if (typeof run_again === 'undefined') { + run_again = true + } + + if (!this.is_running) return + + const { seconds: secondsWas } = this.display + this.update() + + if (run_again) { + const delay = (this.end - Date.now() > 10000) ? 1000 : 250 + this.blinkColon(secondsWas) + this.timeout = setTimeout(this.tick.bind(this), delay) + } + } + + blinkColon (secondsWas) { + // don't blink unless option is set + if (!this.blink_colon) return + // warn_when always updates the seconds + if (this.warn_when > 0 && Date.now() + this.warn_when > this.end) { + this.element.classList.remove('blink-colon') + return + } + const { seconds: secondsIs } = this.display + if (secondsIs > 10 || secondsWas !== secondsIs) { + this.element.classList.toggle('blink-colon') + } + } + + update (force) { + if (typeof force === 'undefined') { + force = false + } + + const { remaining, minutes, seconds } = this.remainingTime() + + const setRemainingTime = (selector, time) => { + const timeContainer = this.element.querySelector(selector) + if (!timeContainer) return + time = Math.max(time, 0) + timeContainer.innerText = String(time).padStart(2, 0) + } + + if (this.is_running && remaining < 0.25) { + this.stop() + setRemainingTime('.minutes', 0) + setRemainingTime('.seconds', 0) + this.playSound() + return + } + + const should_update = force || + Math.round(remaining) < this.warn_when || + Math.round(remaining) % this.update_every === 0 + + if (should_update) { + this.element.classList.toggle('warning', remaining <= this.warn_when) + this.display = { minutes, seconds } + setRemainingTime('.minutes', minutes) + setRemainingTime('.seconds', seconds) + } + } + + stop () { + const { remaining } = this.remainingTime() + if (remaining > 1) { + this.remaining = remaining + } + this.element.classList.remove('running') + this.element.classList.remove('warning') + this.element.classList.remove('blink-colon') + this.element.classList.add('finished') + this.is_running = false + this.end = null + this.reportStateToShiny('stop') + this.timeout = clearTimeout(this.timeout) + } + + reset () { + this.stop() + this.remaining = null + this.update(true) + this.reportStateToShiny('reset') + this.element.classList.remove('finished') + this.element.classList.remove('warning') + } + + setValues (opts) { + if (typeof opts.warn_when !== 'undefined') { + this.warn_when = opts.warn_when + } + if (typeof opts.update_every !== 'undefined') { + this.update_every = opts.update_every + } + if (typeof opts.blink_colon !== 'undefined') { + this.blink_colon = opts.blink_colon + if (!opts.blink_colon) { + this.element.classList.remove('blink-colon') + } + } + if (typeof opts.play_sound !== 'undefined') { + this.play_sound = opts.play_sound + } + if (typeof opts.duration !== 'undefined') { + this.duration = opts.duration + if (this.is_running) { + this.reset() + this.start() + } + } + this.reportStateToShiny('update') + this.update(true) + } + + bumpTimer (val, round) { + round = typeof round === 'boolean' ? round : true + const { remaining } = this.remainingTime() + let newRemaining = remaining + val + if (newRemaining <= 0) { + this.setRemaining(0) + this.stop() + return + } + if (round && newRemaining > 10) { + newRemaining = Math.round(newRemaining / 5) * 5 + } + this.setRemaining(newRemaining) + this.reportStateToShiny(val > 0 ? 'bumpUp' : 'bumpDown') + this.update(true) + } + + bumpUp (val) { + if (!this.is_running) { + console.error('timer is not running') + return + } + this.bumpTimer( + val || this.bumpIncrementValue(), + typeof val === 'undefined' + ) + } + + bumpDown (val) { + if (!this.is_running) { + console.error('timer is not running') + return + } + this.bumpTimer( + val || -1 * this.bumpIncrementValue(), + typeof val === 'undefined' + ) + } + + setRemaining (val) { + if (!this.is_running) { + console.error('timer is not running') + return + } + this.end = Date.now() + val * 1000 + this.update(true) + } + + playSound () { + let url = this.play_sound + if (!url) return + if (typeof url === 'boolean') { + const src = this.src_location + ? this.src_location.replace('/countdown.js', '') + : 'libs/countdown' + url = src + '/smb_stage_clear.mp3' + } + const sound = new Audio(url) + sound.play() + } + + bumpIncrementValue (val) { + val = val || this.remainingTime().remaining + if (val <= 30) { + return 5 + } else if (val <= 300) { + return 15 + } else if (val <= 3000) { + return 30 + } else { + return 60 + } + } + + reportStateToShiny (action) { + if (!window.Shiny) return + + const inputId = this.element.id + const data = { + event: { + action, + time: new Date().toISOString() + }, + timer: { + is_running: this.is_running, + end: this.end ? new Date(this.end).toISOString() : null, + remaining: this.remainingTime() + } + } + + function shinySetInputValue () { + if (!window.Shiny.setInputValue) { + setTimeout(shinySetInputValue, 100) + return + } + window.Shiny.setInputValue(inputId, data) + } + + shinySetInputValue() + } +} + +(function () { + const CURRENT_SCRIPT = document.currentScript.getAttribute('src') + + document.addEventListener('DOMContentLoaded', function () { + const els = document.querySelectorAll('.countdown') + if (!els || !els.length) { + return + } + els.forEach(function (el) { + el.countdown = new CountdownTimer(el, { src_location: CURRENT_SCRIPT }) + }) + + if (window.Shiny) { + Shiny.addCustomMessageHandler('countdown:update', function (x) { + if (!x.id) { + console.error('No `id` provided, cannot update countdown') + return + } + const el = document.getElementById(x.id) + el.countdown.setValues(x) + }) + + Shiny.addCustomMessageHandler('countdown:start', function (id) { + const el = document.getElementById(id) + if (!el) return + el.countdown.start() + }) + + Shiny.addCustomMessageHandler('countdown:stop', function (id) { + const el = document.getElementById(id) + if (!el) return + el.countdown.stop() + }) + + Shiny.addCustomMessageHandler('countdown:reset', function (id) { + const el = document.getElementById(id) + if (!el) return + el.countdown.reset() + }) + + Shiny.addCustomMessageHandler('countdown:bumpUp', function (id) { + const el = document.getElementById(id) + if (!el) return + el.countdown.bumpUp() + }) + + Shiny.addCustomMessageHandler('countdown:bumpDown', function (id) { + const el = document.getElementById(id) + if (!el) return + el.countdown.bumpDown() + }) + } + }) +})() diff --git a/class/3-quarto-plotting/libs/countdown-0.4.0/smb_stage_clear.mp3 b/class/3-quarto-plotting/libs/countdown-0.4.0/smb_stage_clear.mp3 new file mode 100644 index 0000000..da2ddc2 Binary files /dev/null and b/class/3-quarto-plotting/libs/countdown-0.4.0/smb_stage_clear.mp3 differ diff --git a/class/3-quarto-plotting/libs/countdown/countdown.css b/class/3-quarto-plotting/libs/countdown/countdown.css new file mode 100644 index 0000000..ce7c69e --- /dev/null +++ b/class/3-quarto-plotting/libs/countdown/countdown.css @@ -0,0 +1,67 @@ +.countdown { + background: inherit; + position: absolute; + cursor: pointer; + font-size: 2em; + line-height: 1; + border-color: #ddd; + border-width: 3px; + border-style: solid; + border-radius: 15px; + box-shadow: 0px 4px 10px 0px rgba(50, 50, 50, 0.4); + -webkit-box-shadow: 0px 4px 10px 0px rgba(50, 50, 50, 0.4); + margin: 0.6em; + padding: 10px 15px; + text-align: center; +} +.countdown { + display: flex; + align-items: center; + justify-content: center; +} +.countdown .countdown-time { + background: none; + font-size: 100%; + padding: 0; +} +.countdown-digits { + color: inherit; +} +.countdown.running { + border-color: #3C9A5F; + background-color: #43AC6A; +} +.countdown.running .countdown-digits { + color: #102B1A; +} +.countdown.finished { + border-color: #D83A20; + background-color: #F04124; +} +.countdown.finished .countdown-digits { + color: #3C1009; +} +.countdown.running.warning { + border-color: #CFAE24; + background-color: #E6C229; +} +.countdown.running.warning .countdown-digits { + color: #39300A; +} + +@-webkit-keyframes blink { + from {opacity: 1} + 50% {opacity: 0.1} + to {opacity: 1} +} + +@keyframes blink { + from {opacity: 1} + 50% {opacity: 0.1} + to {opacity: 1} +} + +.countdown.running.blink-colon .countdown-digits.colon { + -webkit-animation: blink 2s steps(1, end) 0s infinite; + animation: blink 2s steps(1, end) 0s infinite; +} diff --git a/class/3-quarto-plotting/libs/countdown/countdown.js b/class/3-quarto-plotting/libs/countdown/countdown.js new file mode 100644 index 0000000..0423eaa --- /dev/null +++ b/class/3-quarto-plotting/libs/countdown/countdown.js @@ -0,0 +1,141 @@ +var counters = {timer: {}}; +var update_timer = function(timer, force = false) { + var secs = timer.value; + + // check if we should update timer or not + noup = timer.div.className.match(/noupdate-\d+/); + if (!force && noup != null) { + noup = parseInt(noup[0].match(/\d+$/)); + if (secs > noup * 2 && secs % noup > 0) { return; } + } + + // should we apply or remove warning class? + warnwhen = timer.div.dataset.warnwhen; + if (warnwhen && warnwhen > 0) { + if (secs <= warnwhen && !timer.div.classList.contains("warning")) { + timer.div.classList.add("warning"); + } else if (secs > warnwhen && timer.div.classList.contains("warning")) { + timer.div.classList.remove("warning"); + } + } + + var mins = Math.floor(secs / 60); // 1 min = 60 secs + secs -= mins * 60; + + // Update HTML + timer.min.innerHTML = String(mins).padStart(2, 0); + timer.sec.innerHTML = String(secs).padStart(2, 0); +} +var countdown = function (e) { + target = e.target; + if (target.classList.contains("countdown-digits")) { + target = target.parentElement; + } + if (target.tagName == "CODE") { + target = target.parentElement; + } + + // Init counter + if (!counters.timer.hasOwnProperty(target.id)) { + counters.timer[target.id] = {}; + // Set the containers + counters.timer[target.id].min = target.getElementsByClassName("minutes")[0]; + counters.timer[target.id].sec = target.getElementsByClassName("seconds")[0]; + counters.timer[target.id].div = target; + } + + if (!counters.timer[target.id].running) { + if (!counters.timer[target.id].end) { + counters.timer[target.id].end = parseInt(counters.timer[target.id].min.innerHTML) * 60; + counters.timer[target.id].end += parseInt(counters.timer[target.id].sec.innerHTML); + } + + counters.timer[target.id].value = counters.timer[target.id].end; + update_timer(counters.timer[target.id]); + if (counters.ticker) counters.timer[target.id].value += 1; + + // Start if not past end date + if (counters.timer[target.id].value > 0) { + base_class = target.className.replace(/\s?(running|finished)/, "") + target.className = base_class + " running"; + counters.timer[target.id].running = true; + + if (!counters.ticker) { + counters.ticker = setInterval(counter_update_all, 1000); + } + } + } else { + // Bump timer value if running & clicked + counters.timer[target.id].value += counter_bump_increment(counters.timer[target.id].end); + update_timer(counters.timer[target.id], force = true); + counters.timer[target.id].value += 1; + } +}; + +var counter_bump_increment = function(val) { + if (val <= 30) { + return 5; + } else if (val <= 300) { + return 15; + } else if (val <= 3000) { + return 30; + } else { + return 60; + } +} + +var counter_update_all = function() { + // Iterate over all running timers + for (var i in counters.timer) { + // Stop if passed end time + console.log(counters.timer[i].id) + counters.timer[i].value--; + if (counters.timer[i].value <= 0) { + counters.timer[i].min.innerHTML = "00"; + counters.timer[i].sec.innerHTML = "00"; + counters.timer[i].div.className = counters.timer[i].div.className.replace("running", "finished"); + counters.timer[i].running = false; + } else { + // Update + update_timer(counters.timer[i]); + + // Play countdown sound if data-audio=true on container div + let audio = counters.timer[i].div.dataset.audio + if (audio && counters.timer[i].value == 5) { + counter_play_sound(audio); + } + } + } + + // If no more running timers, then clear ticker + var timerIsRunning = false; + for (var t in counters.timer) { + timerIsRunning = timerIsRunning || counters.timer[t].running + } + if (!timerIsRunning) { + clearInterval(counters.ticker); + counters.ticker = null; + } +} + +var counter_play_sound = function(url) { + if (typeof url === 'boolean') { + url = 'libs/countdown/smb_stage_clear.mp3'; + } + sound = new Audio(url); + sound.play(); +} + +var counter_addEventListener = function() { + if (!document.getElementsByClassName("countdown").length) { + setTimeout(counter_addEventListener, 2); + return; + } + var counter_divs = document.getElementsByClassName("countdown"); + console.log(counter_divs); + for (var i = 0; i < counter_divs.length; i++) { + counter_divs[i].addEventListener("click", countdown, false); + } +}; + +counter_addEventListener(); diff --git a/class/3-quarto-plotting/libs/countdown/smb_stage_clear.mp3 b/class/3-quarto-plotting/libs/countdown/smb_stage_clear.mp3 new file mode 100644 index 0000000..da2ddc2 Binary files /dev/null and b/class/3-quarto-plotting/libs/countdown/smb_stage_clear.mp3 differ diff --git a/class/3-quarto-plotting/libs/header-attrs-2.21/header-attrs.js b/class/3-quarto-plotting/libs/header-attrs-2.21/header-attrs.js new file mode 100644 index 0000000..dd57d92 --- /dev/null +++ b/class/3-quarto-plotting/libs/header-attrs-2.21/header-attrs.js @@ -0,0 +1,12 @@ +// Pandoc 2.9 adds attributes on both header and div. We remove the former (to +// be compatible with the behavior of Pandoc < 2.8). +document.addEventListener('DOMContentLoaded', function(e) { + var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); + var i, h, a; + for (i = 0; i < hs.length; i++) { + h = hs[i]; + if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 + a = h.attributes; + while (a.length > 0) h.removeAttribute(a[0].name); + } +}); diff --git a/class/3-quarto-plotting/libs/header-attrs/header-attrs.js b/class/3-quarto-plotting/libs/header-attrs/header-attrs.js new file mode 100644 index 0000000..dd57d92 --- /dev/null +++ b/class/3-quarto-plotting/libs/header-attrs/header-attrs.js @@ -0,0 +1,12 @@ +// Pandoc 2.9 adds attributes on both header and div. We remove the former (to +// be compatible with the behavior of Pandoc < 2.8). +document.addEventListener('DOMContentLoaded', function(e) { + var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); + var i, h, a; + for (i = 0; i < hs.length; i++) { + h = hs[i]; + if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 + a = h.attributes; + while (a.length > 0) h.removeAttribute(a[0].name); + } +}); diff --git a/class/3-quarto-plotting/libs/panelset/panelset.css b/class/3-quarto-plotting/libs/panelset/panelset.css new file mode 100644 index 0000000..6665f55 --- /dev/null +++ b/class/3-quarto-plotting/libs/panelset/panelset.css @@ -0,0 +1,227 @@ +/* prefixed by https://autoprefixer.github.io (PostCSS: v7.0.23, autoprefixer: v9.7.3) */ + +.panelset { + width: 100%; + position: relative; + --panel-tabs-border-bottom: #ddd; + --panel-tabs-sideways-max-width: 25%; + --panel-tab-foreground: currentColor; + --panel-tab-background: unset; + --panel-tab-active-foreground: currentColor; + --panel-tab-active-background: unset; + --panel-tab-hover-foreground: currentColor; + --panel-tab-hover-background: unset; + --panel-tab-active-border-color: currentColor; + --panel-tab-hover-border-color: currentColor; + --panel-tab-inactive-opacity: 0.5; + --panel-tab-font-family: inherit; +} + +.panelset * { + box-sizing: border-box; +} + +.panelset .panel-tabs { + display: -webkit-box; + display: flex; + flex-wrap: wrap; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + flex-direction: row; + -webkit-box-pack: start; + justify-content: start; + -webkit-box-align: center; + align-items: center; + overflow-y: visible; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + padding: 0 0 2px 0; + box-shadow: inset 0 -2px 0px var(--panel-tabs-border-bottom); +} + +.panelset .panel-tabs * { + -webkit-transition: opacity 0.5s ease; + transition: opacity 0.5s ease; +} + +.panelset .panel-tabs .panel-tab { + min-height: 50px; + display: -webkit-box; + display: flex; + -webkit-box-pack: center; + justify-content: center; + -webkit-box-align: center; + align-items: center; + padding: 0.5em 1em; + font-family: var(--panel-tab-font-family); + opacity: var(--panel-tab-inactive-opacity); + border-top: 2px solid transparent; + border-bottom: 2px solid transparent; + margin-bottom: -2px; + color: var(--panel-tab-foreground); + background-color: var(--panel-tab-background); + list-style: none; + z-index: 5; +} + +.panelset .panel-tabs .panel-tab > a { + color: currentColor; + text-decoration: none; + border: none; + width: 100%; + height: 100%; +} + +.panelset .panel-tabs .panel-tab > a:focus { + outline: none; + text-decoration: none; + border: none; +} + +.panelset .panel-tabs .panel-tab > a:hover { + text-decoration: none; + border: none; +} + +.panelset .panel-tabs .panel-tab:hover { + border-bottom-color: var(--panel-tab-hover-border-color); + color: var(--panel-tab-hover-foreground); + background-color: var(--panel-tab-hover-background); + opacity: 1; + cursor: pointer; + z-index: 10; +} + +.panelset .panel-tabs .panel-tab:focus { + outline: none; + color: var(--panel-tab-hover-foreground); + border-bottom-color: var(--panel-tab-hover-border-color); + background-color: var(--panel-tab-hover-background); +} + +.panelset .panel-tabs .panel-tab.panel-tab-active { + border-top-color: var(--panel-tab-active-border-color); + color: var(--panel-tab-active-foreground); + background-color: var(--panel-tab-active-background); + opacity: 1; +} + +.panelset .panel { + display: none; +} + +.panelset .panel-active { + display: block; +} + +/* ---- Sideways Panelset ---- */ + +@media (min-width: 480px) { + .panelset.sideways { + display: flex; + flex-direction: row; + } + + .panelset.sideways .panel-tabs { + box-shadow: none; + flex-direction: column; + align-items: start; + margin: 0; + margin-right: 1em; + border-right: 2px solid var(--panel-tabs-border-bottom); + max-width: var(--panel-tabs-sideways-max-width); + } + + .panelset.sideways .panel { + max-width: calc(100% - var(--panel-tabs-sideways-max-width) - 1em); + } + + .panelset.sideways .panel-tabs .panel-tab { + border-top: unset; + border-bottom: unset; + padding-left: 0; + } + + .panelset.sideways.right { + flex-direction: row-reverse; + justify-content: space-between; + } + + .panelset.sideways.right { + text-align: inherit; + } + + .panelset.sideways.right .panel-tabs { + align-items: end; + margin-right: 0; + margin-left: 1em; + border-right: unset; + border-left: 2px solid var(--panel-tabs-border-bottom); + } + + .panelset.sideways.right .panel-tabs .panel-tab { + padding-left: 1em; + width: 100%; + } + + .panelset.sideways.right .panel-tabs .panel-tab a { + text-align: right; + } +} + +/* + This next part repeats the same CSS inside the @media query above but with + remarkjs-specific classes to ensure that sideways panelsets are always used. + In the future, we could use @container queries instead once they're availble. +*/ + +.remark-container .panelset.sideways { + display: flex; + flex-direction: row; +} + +.remark-container .panelset.sideways .panel-tabs { + box-shadow: none; + flex-direction: column; + align-items: start; + margin: 0; + margin-right: 1em; + border-right: 2px solid var(--panel-tabs-border-bottom); + max-width: var(--panel-tabs-sideways-max-width); +} + +.remark-container .panelset.sideways .panel { + max-width: calc(100% - var(--panel-tabs-sideways-max-width) - 1em); +} + +.remark-container .panelset.sideways .panel-tabs .panel-tab { + border-top: unset; + border-bottom: unset; + padding-left: 0; +} + +.remark-container .panelset.sideways.right { + flex-direction: row-reverse; + justify-content: space-between; +} + +.remark-container .panelset.sideways.right { + text-align: inherit; +} + +.remark-container .panelset.sideways.right .panel-tabs { + align-items: end; + margin-right: 0; + margin-left: 1em; + border-right: unset; + border-left: 2px solid var(--panel-tabs-border-bottom); +} + +.remark-container .panelset.sideways.right .panel-tabs .panel-tab { + padding-left: 1em; + width: 100%; +} + +.remark-container .panelset.sideways.right .panel-tabs .panel-tab a { + text-align: right; +} diff --git a/class/3-quarto-plotting/libs/panelset/panelset.js b/class/3-quarto-plotting/libs/panelset/panelset.js new file mode 100644 index 0000000..730d54d --- /dev/null +++ b/class/3-quarto-plotting/libs/panelset/panelset.js @@ -0,0 +1,325 @@ +/* global slideshow */ +(function () { + const ready = function (fn) { + /* MIT License Copyright (c) 2016 Nuclei */ + /* https://github.com/nuclei/readyjs */ + const completed = () => { + document.removeEventListener('DOMContentLoaded', completed) + window.removeEventListener('load', completed) + fn() + } + if (document.readyState !== 'loading') { + setTimeout(fn) + } else { + document.addEventListener('DOMContentLoaded', completed) + window.addEventListener('load', completed) + } + } + + ready(function () { + [...document.querySelectorAll('.panel-name')] + .map(el => el.textContent.trim()) + + const panelIds = {} + + const uniquePanelId = (name) => { + name = encodeURIComponent(name.toLowerCase().replace(/[\s]/g, '-')) + if (Object.keys(panelIds).includes(name)) { + name += ++panelIds[name] + } else { + panelIds[name] = 1 + } + return name + } + + const identifyPanelName = (item) => { + let name = 'Panel' + + // If the item doesn't have a parent element, then we've already processed + // it, probably because we're in an Rmd, and it's been removed from the DOM + if (!item.parentElement) { + return + } + + // In R Markdown when header-attrs.js is present, we may have found a + // section header but the class attributes won't be duplicated on the tag + if ( + (item.tagName === 'SECTION' || item.classList.contains('section')) && + /^H[1-6]/.test(item.children[0].tagName) + ) { + name = item.children[0].textContent + item.classList.remove('panel-name') + item.removeChild(item.children[0]) + return name + } + + const nameDiv = item.querySelector('.panel-name') + if (!nameDiv) return name + + // In remarkjs the .panel-name span might be in a paragraph tag + // and if the

is empty, we'll remove it + if ( + nameDiv.tagName === 'SPAN' && + nameDiv.parentNode.tagName === 'P' && + nameDiv.textContent === nameDiv.parentNode.textContent + ) { + name = nameDiv.textContent + item.removeChild(nameDiv.parentNode) + return name + } + + // If none of the above, remove the nameDiv and return the name + name = nameDiv.textContent + nameDiv.parentNode.removeChild(nameDiv) + return name + } + + const processPanelItem = (item) => { + const name = identifyPanelName(item) + if (!name) { + return null + } + return { name, content: item.children, id: uniquePanelId(name) } + } + + const getCurrentPanelFromUrl = (panelset) => { + const params = new URLSearchParams(window.location.search) + return params.get(panelset) + } + + const reflowPanelSet = (panels, idx) => { + const res = document.createElement('div') + res.className = 'panelset' + res.id = 'panelset' + (idx > 0 ? idx : '') + const panelSelected = getCurrentPanelFromUrl(res.id) + + // create header row + const headerRow = document.createElement('ul') + headerRow.className = 'panel-tabs' + headerRow.setAttribute('role', 'tablist') + panels + .map((p, idx) => { + const panelHeaderItem = document.createElement('li') + panelHeaderItem.className = 'panel-tab' + panelHeaderItem.setAttribute('role', 'tab') + const thisPanelIsActive = panelSelected ? panelSelected === p.id : idx === 0 + if (thisPanelIsActive) { + panelHeaderItem.classList.add('panel-tab-active') + panelHeaderItem.setAttribute('aria-selected', true) + } + panelHeaderItem.tabIndex = 0 + panelHeaderItem.id = res.id + '_' + p.id // #panelsetid_panelid + + const panelHeaderLink = document.createElement('a') + panelHeaderLink.href = '?' + res.id + '=' + p.id + '#' + panelHeaderItem.id + panelHeaderLink.setAttribute('onclick', 'return false;') + panelHeaderLink.tabIndex = -1 // list item is tabable, not link + panelHeaderLink.innerHTML = p.name + panelHeaderLink.setAttribute('aria-controls', p.id) + + panelHeaderItem.appendChild(panelHeaderLink) + return panelHeaderItem + }) + .forEach(el => headerRow.appendChild(el)) + + res.appendChild(headerRow) + + panels + .map((p, idx) => { + const panelContent = document.createElement('section') + panelContent.className = 'panel' + panelContent.setAttribute('role', 'tabpanel') + const thisPanelIsActive = panelSelected ? panelSelected === p.id : idx === 0 + panelContent.classList.toggle('panel-active', thisPanelIsActive) + panelContent.id = p.id + panelContent.setAttribute('aria-labelledby', p.id) + Array.from(p.content).forEach(el => panelContent.appendChild(el)) + return panelContent + }) + .forEach(el => res.appendChild(el)) + + return res + } + + /* + * Update selected panel for panelset or delete panelset from query string + * + * @param panelset Panelset ID to update in the search params + * @param panel Panel ID of selected panel in panelset, or null to delete from search params + * @param params Current params object, or params from window.location.search + */ + function updateSearchParams (panelset, panel, params = new URLSearchParams(window.location.search)) { + if (panel) { + params.set(panelset, panel) + } else { + params.delete(panelset) + } + return params + } + + /* + * Update the URL to match params + */ + const updateUrl = (params) => { + if (typeof params === 'undefined') return + params = params.toString() ? ('?' + params.toString()) : '' + const { pathname, hash } = window.location + const uri = pathname + params + hash + window.history.replaceState(uri, '', uri) + } + + const togglePanel = (clicked) => { + if (clicked.nodeName.toUpperCase() === 'A') { + clicked = clicked.parentElement + } + if (!clicked.classList.contains('panel-tab')) return + if (clicked.classList.contains('panel-tab-active')) return + + const tabs = clicked.parentNode + .querySelectorAll('.panel-tab') + const panels = clicked.parentNode.parentNode + .querySelectorAll('.panel') + const panelTabClicked = clicked.children[0].getAttribute('aria-controls') + const panelClicked = clicked.parentNode.parentNode.id + + Array.from(tabs) + .forEach(t => { + t.classList.remove('panel-tab-active') + t.removeAttribute('aria-selected') + }) + Array.from(panels) + .forEach(p => { + const active = p.id === panelTabClicked + p.classList.toggle('panel-active', active) + // make inactive panels inaccessible by keyboard navigation + if (active) { + p.removeAttribute('tabIndex') + p.removeAttribute('aria-hidden') + } else { + p.setAttribute('tabIndex', -1) + p.setAttribute('aria-hidden', true) + } + }) + + clicked.classList.add('panel-tab-active') + clicked.setAttribute('aria-selected', true) + + // emit window resize event to trick html widgets into fitting to the panel width + window.dispatchEvent(new Event('resize')) + + // update query string + const params = updateSearchParams(panelClicked, panelTabClicked) + updateUrl(params) + } + + const initPanelSet = (panelset, idx) => { + let panels = Array.from(panelset.querySelectorAll('.panel')) + if (!panels.length && panelset.matches('.section[class*="level"]')) { + // we're in tabset-alike R Markdown + const panelsetLevel = [...panelset.classList] + .filter(s => s.match(/^level/))[0] + .replace('level', '') + + // move children that aren't inside a section up above the panelset + Array.from(panelset.children).forEach(function (el) { + if (el.matches('div.section[class*="level"]')) return + panelset.parentElement.insertBefore(el, panelset) + }) + + // panels are all .sections with .level + const panelLevel = +panelsetLevel + 1 + panels = Array.from(panelset.querySelectorAll(`.section.level${panelLevel}`)) + } + + if (!panels.length) return + + const contents = panels.map(processPanelItem).filter(o => o !== null) + const newPanelSet = reflowPanelSet(contents, idx) + newPanelSet.classList = panelset.classList + panelset.parentNode.insertBefore(newPanelSet, panelset) + panelset.parentNode.removeChild(panelset) + + // click and touch events + const panelTabs = newPanelSet.querySelector('.panel-tabs'); + ['click', 'touchend'].forEach(eventType => { + panelTabs.addEventListener(eventType, function (ev) { + togglePanel(ev.target) + ev.stopPropagation() + }) + }) + panelTabs.addEventListener('touchmove', function (ev) { + ev.preventDefault() + }) + + // key events + newPanelSet + .querySelector('.panel-tabs') + .addEventListener('keydown', (ev) => { + const self = ev.currentTarget.querySelector('.panel-tab-active') + if (ev.code === 'Space' || ev.code === 'Enter') { + togglePanel(ev.target) + ev.stopPropagation() + } else if (ev.code === 'ArrowLeft' && self.previousSibling) { + togglePanel(self.previousSibling) + self.previousSibling.focus() + ev.stopPropagation() + } else if (ev.code === 'ArrowRight' && self.nextSibling) { + togglePanel(self.nextSibling) + self.nextSibling.focus() + ev.stopPropagation() + } + }) + + return panels + } + + // initialize panels + Array.from(document.querySelectorAll('.panelset')).map(initPanelSet) + + if (typeof slideshow !== 'undefined') { + const getVisibleActivePanelInfo = () => { + const slidePanels = document.querySelectorAll('.remark-visible .panel-tab-active') + + if (!slidePanels.length) return null + + return slidePanels.map(panel => { + return { + panel, + panelId: panel.children[0].getAttribute('aria-controls'), + panelSetId: panel.parentNode.parentNode.id + } + }) + } + + slideshow.on('hideSlide', slide => { + // clear focus if we had a panel-tab selected + document.activeElement.blur() + + // clear search query for panelsets in current slide + const params = [...document.querySelectorAll('.remark-visible .panelset')] + .reduce(function (params, panelset) { + return updateSearchParams(panelset.id, null, params) + }, new URLSearchParams(window.location.search)) + + updateUrl(params) + }) + + slideshow.on('afterShowSlide', slide => { + const slidePanels = getVisibleActivePanelInfo() + + if (slidePanels) { + // only first panel gets focus + slidePanels[0].panel.focus() + // but still update the url to reflect all active panels + const params = slidePanels.reduce( + function (params, { panelId, panelSetId }) { + return updateSearchParams(panelSetId, panelId, params) + }, + new URLSearchParams(window.location.search) + ) + updateUrl(params) + } + }) + } + }) +})() diff --git a/class/3-quarto-plotting/libs/remark-css-0.0.1/default.css b/class/3-quarto-plotting/libs/remark-css-0.0.1/default.css new file mode 100644 index 0000000..d37bfd2 --- /dev/null +++ b/class/3-quarto-plotting/libs/remark-css-0.0.1/default.css @@ -0,0 +1,72 @@ +a, a > code { + color: rgb(249, 38, 114); + text-decoration: none; +} +.footnote { + position: absolute; + bottom: 3em; + padding-right: 4em; + font-size: 90%; +} +.remark-code-line-highlighted { background-color: #ffff88; } + +.inverse { + background-color: #272822; + color: #d6d6d6; + text-shadow: 0 0 20px #333; +} +.inverse h1, .inverse h2, .inverse h3 { + color: #f3f3f3; +} +/* Two-column layout */ +.left-column { + color: #777; + width: 20%; + height: 92%; + float: left; +} +.left-column h2:last-of-type, .left-column h3:last-child { + color: #000; +} +.right-column { + width: 75%; + float: right; + padding-top: 1em; +} +.pull-left { + float: left; + width: 47%; +} +.pull-right { + float: right; + width: 47%; +} +.pull-right + * { + clear: both; +} +img, video, iframe { + max-width: 100%; +} +blockquote { + border-left: solid 5px lightgray; + padding-left: 1em; +} +.remark-slide table { + margin: auto; + border-top: 1px solid #666; + border-bottom: 1px solid #666; +} +.remark-slide table thead th { border-bottom: 1px solid #ddd; } +th, td { padding: 5px; } +.remark-slide thead, .remark-slide tfoot, .remark-slide tr:nth-child(even) { background: #eee } + +@page { margin: 0; } +@media print { + .remark-slide-scaler { + width: 100% !important; + height: 100% !important; + transform: scale(1) !important; + top: 0 !important; + left: 0 !important; + } +} diff --git a/class/3-quarto-plotting/libs/remark-css/default.css b/class/3-quarto-plotting/libs/remark-css/default.css new file mode 100644 index 0000000..d37bfd2 --- /dev/null +++ b/class/3-quarto-plotting/libs/remark-css/default.css @@ -0,0 +1,72 @@ +a, a > code { + color: rgb(249, 38, 114); + text-decoration: none; +} +.footnote { + position: absolute; + bottom: 3em; + padding-right: 4em; + font-size: 90%; +} +.remark-code-line-highlighted { background-color: #ffff88; } + +.inverse { + background-color: #272822; + color: #d6d6d6; + text-shadow: 0 0 20px #333; +} +.inverse h1, .inverse h2, .inverse h3 { + color: #f3f3f3; +} +/* Two-column layout */ +.left-column { + color: #777; + width: 20%; + height: 92%; + float: left; +} +.left-column h2:last-of-type, .left-column h3:last-child { + color: #000; +} +.right-column { + width: 75%; + float: right; + padding-top: 1em; +} +.pull-left { + float: left; + width: 47%; +} +.pull-right { + float: right; + width: 47%; +} +.pull-right + * { + clear: both; +} +img, video, iframe { + max-width: 100%; +} +blockquote { + border-left: solid 5px lightgray; + padding-left: 1em; +} +.remark-slide table { + margin: auto; + border-top: 1px solid #666; + border-bottom: 1px solid #666; +} +.remark-slide table thead th { border-bottom: 1px solid #ddd; } +th, td { padding: 5px; } +.remark-slide thead, .remark-slide tfoot, .remark-slide tr:nth-child(even) { background: #eee } + +@page { margin: 0; } +@media print { + .remark-slide-scaler { + width: 100% !important; + height: 100% !important; + transform: scale(1) !important; + top: 0 !important; + left: 0 !important; + } +} diff --git a/class/3-quarto-plotting/libs/shareon/shareon.min.css b/class/3-quarto-plotting/libs/shareon/shareon.min.css new file mode 100644 index 0000000..29ebe89 --- /dev/null +++ b/class/3-quarto-plotting/libs/shareon/shareon.min.css @@ -0,0 +1,5 @@ +/*! + * shareon v1.4.1 by Nikita Karamov + * https://shareon.js.org + */ +.shareon{font-size:0!important}.shareon>*{display:inline-block;position:relative;height:24px;min-width:16px;margin:3px;padding:6px 10px;background-color:#ccc;border-radius:3.33333px;border:none;box-sizing:content-box;color:#fff;line-height:1.5;transition:opacity .3s ease;vertical-align:middle}.shareon>:hover{border:none;cursor:pointer;opacity:.7}.shareon>:not(:empty){font-size:16px;text-decoration:none}.shareon>:not(:empty):before{position:relative;height:100%;width:28px;top:0;left:0;background-position:0 50%}.shareon>:before{display:inline-block;position:absolute;height:20px;width:20px;top:8px;left:8px;background-repeat:no-repeat;background-size:20px 20px;content:"";vertical-align:bottom}.shareon>.facebook{background-color:#1877f2}.shareon>.facebook:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M23.998 12c0-6.628-5.372-12-11.999-12C5.372 0 0 5.372 0 12c0 5.988 4.388 10.952 10.124 11.852v-8.384H7.078v-3.469h3.046V9.356c0-3.008 1.792-4.669 4.532-4.669 1.313 0 2.686.234 2.686.234v2.953H15.83c-1.49 0-1.955.925-1.955 1.874V12h3.328l-.532 3.469h-2.796v8.384c5.736-.9 10.124-5.864 10.124-11.853z'/%3E%3C/svg%3E")}.shareon>.linkedin{background-color:#2867b2}.shareon>.linkedin:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M23.722 23.72h-4.91v-7.692c0-1.834-.038-4.194-2.559-4.194-2.56 0-2.95 1.995-2.95 4.06v7.827H8.394V7.902h4.716v2.157h.063c.659-1.244 2.261-2.556 4.655-2.556 4.974 0 5.894 3.274 5.894 7.535zM.388 7.902h4.923v15.819H.388zM2.85 5.738A2.849 2.849 0 010 2.886a2.851 2.851 0 112.85 2.852z'/%3E%3C/svg%3E")}.shareon>.linkedin:not(:empty):before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z'/%3E%3C/svg%3E")}.shareon>.messenger{background-color:#09f}.shareon>.messenger:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 11.64C0 4.95 5.24 0 12 0s12 4.95 12 11.64-5.24 11.64-12 11.64c-1.21 0-2.38-.16-3.47-.46a.96.96 0 00-.64.05L5.5 23.92a.96.96 0 01-1.35-.85l-.07-2.14a.97.97 0 00-.32-.68A11.39 11.39 0 010 11.64zm8.32-2.19l-3.52 5.6c-.35.53.32 1.14.82.75l3.79-2.87c.26-.2.6-.2.87 0l2.8 2.1c.84.63 2.04.4 2.6-.48l3.52-5.6c.35-.53-.32-1.13-.82-.75l-3.79 2.87c-.25.2-.6.2-.86 0l-2.8-2.1a1.8 1.8 0 00-2.61.48z'/%3E%3C/svg%3E")}.shareon>.odnoklassniki{background-color:#ee8208}.shareon>.odnoklassniki:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M14.505 17.44a11.599 11.599 0 003.6-1.49 1.816 1.816 0 00-1.935-3.073 7.866 7.866 0 01-8.34 0 1.814 1.814 0 00-2.5.565c0 .002 0 .004-.002.005a1.812 1.812 0 00.567 2.5l.002.002c1.105.695 2.322 1.2 3.596 1.488l-3.465 3.465A1.796 1.796 0 006 23.439l.03.03c.344.354.81.53 1.274.53.465 0 .93-.176 1.275-.53L12 20.065l3.404 3.406a1.815 1.815 0 002.566-2.565l-3.465-3.466zM12 12.388a6.202 6.202 0 006.195-6.193C18.195 2.78 15.415 0 12 0S5.805 2.78 5.805 6.197A6.2 6.2 0 0012 12.389zm0-8.757a2.566 2.566 0 010 5.13 2.569 2.569 0 01-2.565-2.564A2.57 2.57 0 0112 3.63z'/%3E%3C/svg%3E")}.shareon>.pinterest{background-color:#ee0023}.shareon>.pinterest:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.017 0C5.396 0 .029 5.367.029 11.987c0 5.079 3.158 9.417 7.618 11.162-.105-.949-.199-2.403.041-3.439.219-.937 1.406-5.957 1.406-5.957s-.359-.72-.359-1.781c0-1.663.967-2.911 2.168-2.911 1.024 0 1.518.769 1.518 1.688 0 1.029-.653 2.567-.992 3.992-.285 1.193.6 2.165 1.775 2.165 2.128 0 3.768-2.245 3.768-5.487 0-2.861-2.063-4.869-5.008-4.869-3.41 0-5.409 2.562-5.409 5.199 0 1.033.394 2.143.889 2.741.099.12.112.225.085.345-.09.375-.293 1.199-.334 1.363-.053.225-.172.271-.401.165-1.495-.69-2.433-2.878-2.433-4.646 0-3.776 2.748-7.252 7.92-7.252 4.158 0 7.392 2.967 7.392 6.923 0 4.135-2.607 7.462-6.233 7.462-1.214 0-2.354-.629-2.758-1.379l-.749 2.848c-.269 1.045-1.004 2.352-1.498 3.146 1.123.345 2.306.535 3.55.535 6.607 0 11.985-5.365 11.985-11.987C23.97 5.39 18.592.026 11.985.026z'/%3E%3C/svg%3E")}.shareon>.pocket{background-color:#ef4154}.shareon>.pocket:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.813 10.259l-5.646 5.419a1.649 1.649 0 01-2.282 0l-5.646-5.419a1.645 1.645 0 012.276-2.376l4.511 4.322 4.517-4.322a1.643 1.643 0 012.326.049 1.64 1.64 0 01-.045 2.326zm5.083-7.546a2.163 2.163 0 00-2.041-1.436H2.179c-.9 0-1.717.564-2.037 1.405-.094.25-.142.511-.142.774v7.245l.084 1.441c.348 3.277 2.047 6.142 4.682 8.139.045.036.094.07.143.105l.03.023a11.899 11.899 0 004.694 2.072c.786.158 1.591.24 2.389.24.739 0 1.481-.067 2.209-.204.088-.029.176-.045.264-.06.023 0 .049-.015.074-.029a12.002 12.002 0 004.508-2.025l.029-.031.135-.105c2.627-1.995 4.324-4.862 4.686-8.148L24 10.678V3.445c0-.251-.031-.5-.121-.742z'/%3E%3C/svg%3E")}.shareon>.reddit{background-color:#ff4500}.shareon>.reddit:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M19.53 1.148a1.83 1.83 0 00-1.667 1.054l-4.372-.928a.522.522 0 00-.358.063.515.515 0 00-.21.297L11.59 7.925c-2.807.086-5.32.909-7.137 2.237a2.668 2.668 0 00-1.815-.737A2.64 2.64 0 000 12.064a2.634 2.634 0 001.563 2.407 4.95 4.95 0 00-.065.803c0 4.053 4.71 7.326 10.537 7.326s10.537-3.273 10.537-7.326a4.548 4.548 0 00-.063-.782 2.732 2.732 0 001.519-2.428 2.64 2.64 0 00-2.639-2.64 2.53 2.53 0 00-1.816.74c-1.796-1.288-4.287-2.134-7.031-2.239l1.204-5.637 3.906.823a1.888 1.888 0 001.878 1.777c1.024 0 1.87-.837 1.88-1.861a1.884 1.884 0 00-1.88-1.88zM7.907 18.066c-.13 0-.254.05-.347.141a.498.498 0 000 .697c1.266 1.267 3.736 1.373 4.454 1.373s3.167-.084 4.454-1.373a.546.546 0 00.044-.697.5.5 0 00-.698 0c-.823.802-2.533 1.099-3.779 1.099s-2.977-.295-3.779-1.099a.49.49 0 00-.349-.142zm-1.932-4.122c0-1.035.844-1.88 1.88-1.88 1.034 0 1.878.843 1.878 1.879S8.89 15.82 7.856 15.82a1.882 1.882 0 01-1.88-1.877zm10.155-1.88c1.035 0 1.88.845 1.88 1.879 0 1.035-.844 1.878-1.879 1.878s-1.879-.843-1.879-1.877c0-1.037.844-1.88 1.878-1.88z' fill-rule='evenodd'/%3E%3C/svg%3E")}.shareon>.telegram{background-color:#179cde}.shareon>.telegram:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1.517 11.31c-.962.382-1.466.756-1.512 1.122-.088.702.924.921 2.196 1.335 1.037.337 2.433.731 3.158.747.658.014 1.393-.257 2.204-.814 5.533-3.735 8.39-5.622 8.57-5.663.126-.029.301-.065.42.04.119.106.107.306.095.36-.101.429-5.3 5.156-5.599 5.467-1.143 1.187-2.443 1.913-.437 3.235 1.735 1.144 2.746 1.873 4.534 3.045 1.142.75 2.039 1.637 3.218 1.529.543-.05 1.104-.56 1.389-2.083.673-3.598 1.996-11.392 2.302-14.604a3.585 3.585 0 00-.034-.8c-.027-.158-.084-.383-.29-.55-.243-.197-.619-.24-.787-.236-.764.013-1.936.42-7.579 2.767C11.39 7.03 7.44 8.73 1.517 11.31z'/%3E%3C/svg%3E")}.shareon>.twitter{background-color:#1da1f2}.shareon>.twitter:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M23.954 4.569a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.691 8.094 4.066 6.13 1.64 3.161a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.061a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.937 4.937 0 004.604 3.417 9.868 9.868 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.054 0 13.999-7.496 13.999-13.986 0-.209 0-.42-.015-.63a9.936 9.936 0 002.46-2.548l-.047-.02z'/%3E%3C/svg%3E")}.shareon>.viber{background-color:#7360f2}.shareon>.viber:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11.398.002C9.473.028 5.331.344 3.014 2.467 1.294 4.177.693 6.698.623 9.82c-.06 3.11-.13 8.95 5.5 10.541v2.42s-.038.97.602 1.17c.79.25 1.24-.499 1.99-1.299l1.4-1.58c3.85.32 6.8-.419 7.14-.529.78-.25 5.181-.811 5.901-6.652.74-6.031-.36-9.831-2.34-11.551l-.01-.002c-.6-.55-3-2.3-8.37-2.32 0 0-.396-.025-1.038-.016zm.067 1.697c.545-.003.88.02.88.02 4.54.01 6.711 1.38 7.221 1.84 1.67 1.429 2.528 4.856 1.9 9.892-.6 4.88-4.17 5.19-4.83 5.4-.28.09-2.88.73-6.152.52 0 0-2.439 2.941-3.199 3.701-.12.13-.26.17-.35.15-.13-.03-.17-.19-.16-.41l.02-4.019c-4.771-1.32-4.491-6.302-4.441-8.902.06-2.6.55-4.732 2-6.172 1.957-1.77 5.475-2.01 7.11-2.02zm.36 2.6a.299.299 0 00-.3.299.3.3 0 00.3.3 5.631 5.631 0 014.03 1.59c1.09 1.06 1.621 2.48 1.641 4.34a.3.3 0 00.3.3v-.009a.3.3 0 00.3-.3 6.451 6.451 0 00-1.81-4.76c-1.19-1.16-2.692-1.76-4.462-1.76zm-3.954.69a.955.955 0 00-.615.12h-.012c-.41.24-.788.54-1.148.94-.27.32-.421.639-.461.949a1.24 1.24 0 00.05.541l.02.01a13.722 13.722 0 001.2 2.6 15.383 15.383 0 002.32 3.171l.03.04.04.03.03.03.03.03a15.603 15.603 0 003.18 2.33c1.32.72 2.122 1.06 2.602 1.2v.01c.14.04.268.06.398.06a1.84 1.84 0 001.102-.472c.39-.35.7-.738.93-1.148v-.01c.23-.43.15-.841-.18-1.121a13.632 13.632 0 00-2.15-1.54c-.51-.28-1.03-.11-1.24.17l-.45.569c-.23.28-.65.24-.65.24l-.012.01c-3.12-.8-3.95-3.959-3.95-3.959s-.04-.43.25-.65l.56-.45c.27-.22.46-.74.17-1.25a13.522 13.522 0 00-1.54-2.15.843.843 0 00-.504-.3zm4.473.89a.3.3 0 00.002.6 3.78 3.78 0 012.65 1.15 3.5 3.5 0 01.9 2.57.3.3 0 00.3.299l.01.012a.3.3 0 00.3-.301c.03-1.19-.34-2.19-1.07-2.99-.73-.8-1.75-1.25-3.05-1.34a.3.3 0 00-.042 0zm.49 1.619a.305.305 0 00-.018.611c.99.05 1.47.55 1.53 1.58a.3.3 0 00.3.29h.01a.3.3 0 00.29-.32c-.07-1.34-.8-2.091-2.1-2.161a.305.305 0 00-.012 0z'/%3E%3C/svg%3E")}.shareon>.vkontakte{background-color:#4680c2}.shareon>.vkontakte:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M23.058 19.504h-2.616c-.99 0-1.297-.787-3.076-2.59-1.55-1.501-2.236-1.704-2.617-1.704-.534 0-.687.153-.687.89v2.363c0 .636-.202 1.017-1.88 1.017-2.77 0-5.845-1.677-8.004-4.804C.925 10.103.034 6.672.034 5.961c0-.381.153-.737.89-.737H3.54c.66 0 .915.305 1.17 1.016 1.295 3.736 3.456 7.014 4.345 7.014.33 0 .483-.153.483-.99V8.399c-.102-1.78-1.042-1.931-1.042-2.566 0-.306.255-.61.66-.61h4.117c.56 0 .762.304.762.964v5.211c0 .558.255.762.407.762.33 0 .61-.204 1.22-.813 1.88-2.11 3.227-5.362 3.227-5.362.178-.381.483-.737 1.145-.737h2.616c.788 0 .966.405.788.965-.33 1.526-3.532 6.048-3.532 6.048-.28.457-.381.66 0 1.17.28.381 1.194 1.169 1.805 1.88 1.118 1.27 1.98 2.338 2.21 3.076.255.735-.128 1.116-.864 1.116z'/%3E%3C/svg%3E")}.shareon>.whatsapp{background-color:#25d366}.shareon>.whatsapp:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg fill='%23fff' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51a12.8 12.8 0 00-.57-.01c-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z'/%3E%3C/svg%3E")} \ No newline at end of file diff --git a/class/3-quarto-plotting/libs/shareon/shareon.min.js b/class/3-quarto-plotting/libs/shareon/shareon.min.js new file mode 100644 index 0000000..8a1d9db --- /dev/null +++ b/class/3-quarto-plotting/libs/shareon/shareon.min.js @@ -0,0 +1,6 @@ +/*! + * shareon v1.4.1 by Nikita Karamov + * https://shareon.js.org + */ + +var shareon=function(){"use strict";var t={facebook:t=>"https://www.facebook.com/sharer/sharer.php?u="+t.url,linkedin:t=>`https://www.linkedin.com/shareArticle?mini=true&url=${t.url}&title=${t.title}`,messenger:t=>`https://www.facebook.com/dialog/send?app_id=3619024578167617&link=${t.url}&redirect_uri=${t.url}`,odnoklassniki:t=>`https://connect.ok.ru/offer?url=${t.url}&title=${t.title}${t.media?"&imageUrl="+t.media:""}`,pinterest:t=>`https://pinterest.com/pin/create/button/?url=${t.url}&description=${t.title}${t.media?"&media="+t.media:""}`,pocket:t=>"https://getpocket.com/edit.php?url="+t.url,reddit:t=>`https://www.reddit.com/submit?title=${t.title}&url=${t.url}`,telegram:t=>`https://telegram.me/share/url?url=${t.url}${t.text?"&text="+t.text:""}`,twitter:t=>`https://twitter.com/intent/tweet?url=${t.url}&text=${t.title}${t.via?"&via="+t.via:""}`,viber:t=>`viber://forward?text=${t.title}%0D%0A${t.url}${t.text?"%0D%0A%0D%0A"+t.text:""}`,vkontakte:t=>`https://vk.com/share.php?url=${t.url}&title=${t.title}${t.media?"&image="+t.media:""}`,whatsapp:t=>`whatsapp://send?text=${t.title}%0D%0A${t.url}${t.text?"%0D%0A%0D%0A"+t.text:""}`};const e=()=>{const e=document.getElementsByClassName("shareon");for(let r=0;r{window.open(o,"_blank","noopener,noreferrer").opener=null});break}}}}}};return window.onload=()=>{e()},e}(); diff --git a/class/3-quarto-plotting/libs/tile-view/tile-view.css b/class/3-quarto-plotting/libs/tile-view/tile-view.css new file mode 100644 index 0000000..e1870da --- /dev/null +++ b/class/3-quarto-plotting/libs/tile-view/tile-view.css @@ -0,0 +1,52 @@ +.remark__tile-view * { + box-sizing: border-box; +} + +.remark__tile-view { + background: lightgray; + position: relative; + width: 100%; + height: 100%; + padding: 3em; + font-size: 18px; + box-sizing: border-box; + overflow: scroll; +} + +.remark__tile-view__header { + text-align: center; +} + +.remark__tile-view__tiles { + display: grid; + /* Set column width in JS */ + /* grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); */ + justify-items: center; +} + +.remark__tile-view__tile { + position: relative; + margin: 0.5em; + padding: 0.5em; +} + +.remark__tile-view__slide-container { + margin: 0 auto; +} + +.remark__tile-view__tile--current { + background: #ffd863; + border: 5px solid #ffd863; + margin: calc(0.5em - 5px); + border-radius: 0; +} + +.remark__tile-view__tile--seen { + opacity: 0.5; +} + +.remark__tile-view__tile:hover { + /* background: #993d70; */ + background: #44bc96; + opacity: 1; +} diff --git a/class/3-quarto-plotting/libs/tile-view/tile-view.js b/class/3-quarto-plotting/libs/tile-view/tile-view.js new file mode 100644 index 0000000..bb31dc2 --- /dev/null +++ b/class/3-quarto-plotting/libs/tile-view/tile-view.js @@ -0,0 +1,177 @@ +/* + * Tile View for remark.js Slides + * + * Garrick Aden-Buie + * + * Inspired and converted to Vanilla JS from + * https://github.com/StephenHesperus/remark-hook/ + * + * Include after remarkjs slides are initialized. + * + */ + +/* global slideshow */ +(function () { + const ready = function (fn) { + /* MIT License Copyright (c) 2016 Nuclei */ + /* https://github.com/nuclei/readyjs */ + const completed = () => { + document.removeEventListener('DOMContentLoaded', completed) + window.removeEventListener('load', completed) + fn() + } + if (document.readyState !== 'loading') { + setTimeout(fn) + } else { + document.addEventListener('DOMContentLoaded', completed) + window.addEventListener('load', completed) + } + } + + ready(function () { + const launchKey = 79 // keycode for O, used to enable tile view + + // Slides container + const remarkSlideShow = document.querySelector('div.remark-slides-area') + + let tileView = document.querySelector('div.remark__tile-view') + if (!tileView) { + tileView = document.createElement('div') + tileView.className = 'remark__tile-view' + } + + const toggleElement = el => { + el.style.display = el.style.display === 'none' ? '' : 'none' + } + + function slideshowResize () { + window.dispatchEvent(new Event('resize')) + } + + const toggleTileView = function () { + toggleElement(tileView) + toggleElement(remarkSlideShow) + + if (tileView.style.display === 'none') { + // tileView is now hidden, go to current slide + slideshow.gotoSlide(tileVars.currentSlideIdx + 1) + + slideshow.resume() + slideshowResize() + } else { + // store current slide index prior to launching tile-view + tileVars.currentSlideIdx = slideshow.getCurrentSlideIndex() + + // set class on seen and current slide and scroll into view + const tiles = tileView.querySelectorAll('.remark__tile-view__tile'); + [...tiles].forEach((tile, idx) => { + tile.classList.toggle( + 'remark__tile-view__tile--seen', + idx < tileVars.currentSlideIdx + ) + tile.classList.toggle( + 'remark__tile-view__tile--current', + idx === tileVars.currentSlideIdx + ) + }) + tiles[tileVars.currentSlideIdx].scrollIntoView({ + behavior: 'smooth', + block: 'center' + }) + + slideshow.pause() + } + } + + const createTileView = ({ minSize = 250, title = document.title } = {}) => { + // Tile view header + const h1 = document.createElement('h1') + h1.className = 'remark__tile-view__header' + h1.innerHTML = title + + tileView.appendChild(h1) + const tiles = document.createElement('div') + tiles.className = 'remark__tile-view__tiles' + tileView.appendChild(tiles) + + // Clone slideshow + const slidesArea = remarkSlideShow.cloneNode(true) + + // Calculate slide scale and tile container size + const slideScaler = slidesArea.querySelector('.remark-slide-scaler') + const slideWidth = parseFloat(slideScaler.style.width.replace('px', '')) + const slideHeight = parseFloat( + slideScaler.style.height.replace('px', '') + ) + const scale = minSize / Math.min(slideWidth, slideHeight) + let tileWidth = Math.round(slideWidth * scale) + let tileHeight = Math.round(slideHeight * scale) + + // convert tileWidth/Height to em relative to base 18px (set in CSS) + tileWidth = tileWidth / 18 + tileHeight = tileHeight / 18 + + tiles.style.gridTemplateColumns = `repeat(auto-fill, minmax(${tileWidth}em, 1fr))` + + const slides = slidesArea.querySelectorAll('.remark-slide-container') + + slides.forEach((slide, slideIndex) => { + let tile = document.createElement('template') + tile.innerHTML = `

+
+
` + tile = tile.content.firstChild + + const tileContainer = tile.querySelector( + '.remark__tile-view__slide-container' + ) + tileContainer.style.width = `${tileWidth}em` + tileContainer.style.height = `${tileHeight}em` + + const thisSlideScaler = slide.querySelector('.remark-slide-scaler') + thisSlideScaler.style.top = '0px' + thisSlideScaler.style.left = '0px' + thisSlideScaler.style.transform = `scale(${scale})` + thisSlideScaler.parentElement.classList.add('remark-visible') + + slide.addEventListener('click', () => { + tileVars.currentSlideIdx = slideIndex + toggleTileView() + }) + + tileContainer.appendChild(slide) + tiles.appendChild(tile) + }) + + document.body.appendChild(tileView) + } + + const tileVars = {} + + document.addEventListener('keydown', ev => { + if (ev.keyCode === launchKey) { + toggleTileView() + } + }) + + const addTileViewHelpText = () => { + const helpTable = document.querySelector( + '.remark-help-content table.light-keys' + ) + if (!helpTable) { + console.error( + 'Could not find remark help table, has remark been initialized?' + ) + return + } + const newRow = document.createElement('tr') + newRow.innerHTML += 'o' + newRow.innerHTML += 'Tile View: Overview of Slides' + helpTable.append(newRow) + } + + createTileView({ minSize: 200 }) + toggleElement(tileView) + addTileViewHelpText() + }) +})() diff --git a/class/3-quarto-plotting/libs/xaringanExtra-clipboard/xaringanExtra-clipboard.css b/class/3-quarto-plotting/libs/xaringanExtra-clipboard/xaringanExtra-clipboard.css new file mode 100644 index 0000000..2a38a37 --- /dev/null +++ b/class/3-quarto-plotting/libs/xaringanExtra-clipboard/xaringanExtra-clipboard.css @@ -0,0 +1,23 @@ +.xaringanextra-clipboard-button { + position: absolute; + top: 0; + right: 0; + font-size: 0.8em; + padding: 0.5em; + display: none; + background-color: transparent; + border: none; + opacity: 0.5; + border-radius: 0; +} + +.xaringanextra-clipboard-button:hover { + background-color: rgba(0, 0, 0, 0.1); + border: none; + opacity: 1; +} + +:hover > .xaringanextra-clipboard-button { + display: block; + transform: translateY(0); +} diff --git a/class/3-quarto-plotting/libs/xaringanExtra-clipboard/xaringanExtra-clipboard.js b/class/3-quarto-plotting/libs/xaringanExtra-clipboard/xaringanExtra-clipboard.js new file mode 100644 index 0000000..0532818 --- /dev/null +++ b/class/3-quarto-plotting/libs/xaringanExtra-clipboard/xaringanExtra-clipboard.js @@ -0,0 +1,98 @@ +/* global slideshow,window,document */ +window.xaringanExtraClipboard = function (selector, text) { + if (!window.ClipboardJS.isSupported()) return + if (!window.xaringanExtraClipboards) window.xaringanExtraClipboards = {} + + const ready = function (fn) { + /* MIT License Copyright (c) 2016 Nuclei */ + /* https://github.com/nuclei/readyjs */ + const completed = () => { + document.removeEventListener('DOMContentLoaded', completed) + window.removeEventListener('load', completed) + fn() + } + if (document.readyState !== 'loading') { + setTimeout(fn) + } else { + document.addEventListener('DOMContentLoaded', completed) + window.addEventListener('load', completed) + } + } + + ready(function () { + const { + button: buttonText = 'Copy Code', + success: successText = 'Copied!', + error: errorText = 'Press Ctrl+C to Copy' + } = text + + const template = '` + + const isRemarkSlideshow = typeof slideshow !== 'undefined' && + Object.prototype.hasOwnProperty.call(slideshow, 'getSlides') + + let siblingSelector = selector || 'pre' + if (!selector && isRemarkSlideshow) { + siblingSelector = '.remark-slides-area ' + siblingSelector + } + + // insert + + +
  • ${getShortTitle()}
  • +
  • + + +
    + + + + + + + +
    +
  • + ` + + const slidesContainer = document.querySelector(':not(html).remark-container') + slidesContainer.appendChild(navbar) + + const btnSlidePrev = document.getElementById('shareagain-slide-prev') + const btnSlideNext = document.getElementById('shareagain-slide-next') + + function toggleSlideButtons (slideIndex) { + if (typeof slideIndex === 'undefined') { + slideIndex = slideshow.getCurrentSlideIndex() + } + + // Toggle next slide button + if (slideIndex + 1 === slideshow.getSlideCount()) { + btnSlideNext.classList.add('disabled') + btnSlideNext.setAttribute('disabled', true) + } else { + btnSlideNext.classList.remove('disabled') + btnSlideNext.removeAttribute('disabled') + } + + // Toggle prev slide button + if (slideIndex === 0) { + btnSlidePrev.classList.add('disabled') + btnSlidePrev.setAttribute('disabled', true) + } else { + btnSlidePrev.classList.remove('disabled') + btnSlidePrev.removeAttribute('disabled') + } + } + + setTimeout(toggleSlideButtons, 100) + + // button click events + btnSlidePrev.addEventListener('click', function (ev) { + slideshow.gotoPreviousSlide() + }) + + btnSlideNext.addEventListener('click', function (ev) { + slideshow.gotoNextSlide() + }) + + // button touch events (block remarkjs slide change on touch) + btnSlidePrev.addEventListener('touchend', function (ev) { + slideshow.gotoPreviousSlide() + ev.stopPropagation() + }) + + btnSlideNext.addEventListener('touchend', function (ev) { + slideshow.gotoNextSlide() + ev.stopPropagation() + }); + + // show/hide share buttons + ['click', 'touchend'].map(function (evType) { + document.getElementById('shareagain-share').addEventListener(evType, function (ev) { + toggleShareButtons() + ev.preventDefault() + ev.stopPropagation() + }) + }) + + navbar.addEventListener('touchend', function (ev) { + ev.preventDefault() + ev.stopPropagation() + }) + + // copy slides link to clipboard + const shareClip = new ClipboardJS('.shareon .link') + shareClip.on('success', function (e) { + const el = document.querySelector('.shareon .link') + el.classList.add('success') + setTimeout(() => el.classList.remove('success'), 2500) + }) + + slideshow.on('afterShowSlide', function (slide) { + toggleSlideButtons(slide.getSlideIndex()) + }) + + function toggleAnimated ({ selector, show, inClass, outClass }) { + const el = document.querySelector(selector) + const isShown = el.classList.contains(inClass) + if (typeof show === 'undefined') { + show = !isShown + } + if (show === isShown) { + return + } + if (show) { + el.classList.remove(outClass) + el.classList.add(inClass) + } else { + el.classList.remove(inClass) + el.classList.add(outClass) + } + } + + let isCurrentlyFullScreen = false + document.addEventListener('fullscreenchange', (event) => { + if (document.fullscreenElement) { + stopAutoHide(false) + isCurrentlyFullScreen = true + } else { + isCurrentlyFullScreen = false + startAutoHide() + } + }) + + function toggleNavBar (show) { + // do nothing if currently fullscreen + if (isCurrentlyFullScreen) return + + toggleAnimated({ selector: '.shareagain-bar ul', show, inClass: 'slideInUp', outClass: 'slideOutDown' }) + if (!show) toggleShareButtons(false) + } + + function toggleShareButtons (show) { + toggleAnimated({ selector: '.shareagain-bar .shareon', show, inClass: 'slideInRight', outClass: 'slideOutRight' }) + } + + // auto hide the share bar when focus is in the slides + let mouseMoveTimer = null + function hideNavDelayed (ev) { + if (mouseMoveTimer) { + clearTimeout(mouseMoveTimer) + } + toggleNavBar(true) + mouseMoveTimer = setTimeout(function () { toggleNavBar(false) }, 2000) + }; + + // toggle toggle full screen + ['click', 'touchend'].map(function (evType) { + document.getElementById('shareagain-fullscreen').addEventListener(evType, function (ev) { + slideshow.toggleFullscreen() + ev.stopPropagation() + }) + }) + + function startAutoHide () { + hideNavDelayed() + slidesContainer.addEventListener('mousemove', hideNavDelayed) + } + + function stopAutoHide (showAfter = false) { + slidesContainer.removeEventListener('mousemove', hideNavDelayed) + clearTimeout(mouseMoveTimer) + toggleNavBar(showAfter) + } + + // auto hide turns on when the mouse comes into the slides area + slidesContainer.addEventListener('mouseenter', function () { + startAutoHide() + }) + + // turn off auto hide when the mouse leaves the slides area + slidesContainer.addEventListener('mouseleave', function () { + stopAutoHide(true) + }); + + // turn off auto hide if the mouse or focus is in the share bar + ['focusin', 'mouseenter'].map(function (evType) { + navbar.addEventListener(evType, function () { + stopAutoHide(true) + }) + }); + + // and turn auto hide back on whne the mouse leaves the share bar + // (if mouse exits out of the slides area, the slidesContainer should fire later) + ['focusout', 'mouseleave'].map(function (evType) { + navbar.addEventListener(evType, function () { + startAutoHide() + }) + }) + } +})() diff --git a/class/3-quarto-plotting/logo.png b/class/3-quarto-plotting/logo.png new file mode 100644 index 0000000..4e791a0 Binary files /dev/null and b/class/3-quarto-plotting/logo.png differ diff --git a/class/3-quarto-plotting/quarto_demo.qmd b/class/3-quarto-plotting/quarto_demo.qmd new file mode 100644 index 0000000..7d18147 --- /dev/null +++ b/class/3-quarto-plotting/quarto_demo.qmd @@ -0,0 +1,111 @@ +--- +title: "A Quick Quarto Demo" +subtitle: "A summary of just a few features" +author: "John Helveston" +format: + html: + toc: true + theme: flatly + self-contained: true +--- + +```{r} +#| label: setup +#| include: false + +knitr::opts_chunk$set( + warning = FALSE, + message = FALSE, + fig.path = "figs/", + fig.width = 7.252, + fig.height = 4, + comment = "#>", + fig.retina = 3 +) + +# Load libraries +library(tidyverse) +library(here) + +# Read in data +bears <- read_csv(here::here('data', 'bear_killings.csv')) +``` + +# Quarto + +This is a Quarto markdown document. Markdown is a simple formatting syntax for authoring HTML, PDF, and MS Word documents. For more details on using Quarto see the [Quarto website](https://quarto.org/). + +When you click the **Render** button a document will be generated that includes both content as well as the output of any embedded R code chunks within the document. + +## Useful tools + +- Quick markdown reference guide: [https://commonmark.org/help/](https://commonmark.org/help/) +- Quick demo guide: [https://markdown-it.github.io/](https://markdown-it.github.io/). +- Online table converter: [http://www.tablesgenerator.com](http://www.tablesgenerator.com/markdown_tables) + +# In-line code + +In-line code allows you to insert R code directly into a sentence. For example, the sum of 3 and 4 is `r 3 + 4`. Here the value of 7 was computed from `3 + 4` and inserted into the sentence. + +You can also use variables. For example, the `bears` data frame has `r nrow(bears)` rows. + +# Code chunks + +Code chunks are places to write longer bits of R code that will be compiled. For example, let's say I wanted to show the first 6 rows of the `bears` data frame - I could write that in a chunk like this: + +```{r} +head(bears) +``` + +Now when compiled, the print out of `head(bears)` will show. + +You can change how the chunk behave by changing some settings inside the `{r}` part of the chunk. For example, if I only want to show the result of the code (and not the code itself), I can set `echo: false`: + +```{r} +#| echo: false + +head(bears) +``` + +If I only want to show the code but not actually run it, I can set `eval: false`: + +```{r} +#| eval: false + +head(bears) +``` + +If I want to the code in the background but not show anything, I can set `include: false`: + +```{r} +#| include: false + +head(bears) +``` + +# Plots + +## Using ggplot + +Here is an example of how to make a plot with ggplot2: + +```{r} +bears %>% + count(month) %>% + ggplot() + + geom_col(aes(x = as.factor(month), y = n)) + + theme_minimal(base_size = 22) + + labs(x = "Month", y = "Count") +``` + +## Inserting an image file + +You can define the side using `out.width`, like this: + +```{r} +#| echo: false +#| out.width: 20% +#| fig.align: center + +knitr::include_graphics("logo.png") +``` diff --git a/class/3-quarto-plotting/topics/0.Rmd b/class/3-quarto-plotting/topics/0.Rmd new file mode 100644 index 0000000..e0b73e0 --- /dev/null +++ b/class/3-quarto-plotting/topics/0.Rmd @@ -0,0 +1,11 @@ + +class: inverse, middle + +# Week `r rmarkdown::metadata$week`: .fancy[`r rmarkdown::metadata$title`] + +### 1. Intro to Quarto + +### QUIZ 1 + +### 2. Intro to ggplot2 +### 3. Project attributes & levels diff --git a/class/3-quarto-plotting/topics/1.Rmd b/class/3-quarto-plotting/topics/1.Rmd new file mode 100644 index 0000000..f2120e4 --- /dev/null +++ b/class/3-quarto-plotting/topics/1.Rmd @@ -0,0 +1,11 @@ + +class: inverse, middle + +# Week `r rmarkdown::metadata$week`: .fancy[`r rmarkdown::metadata$title`] + +### 1. .orange[Intro to Quarto] + +### QUIZ 1 + +### 2. Intro to ggplot2 +### 3. Project attributes & levels diff --git a/class/3-quarto-plotting/topics/2.Rmd b/class/3-quarto-plotting/topics/2.Rmd new file mode 100644 index 0000000..2384839 --- /dev/null +++ b/class/3-quarto-plotting/topics/2.Rmd @@ -0,0 +1,11 @@ + +class: inverse, middle + +# Week `r rmarkdown::metadata$week`: .fancy[`r rmarkdown::metadata$title`] + +### 1. Intro to Quarto + +### QUIZ 1 + +### 2. .orange[Intro to ggplot2] +### 3. Project attributes & levels diff --git a/class/3-quarto-plotting/topics/3.Rmd b/class/3-quarto-plotting/topics/3.Rmd new file mode 100644 index 0000000..61474a9 --- /dev/null +++ b/class/3-quarto-plotting/topics/3.Rmd @@ -0,0 +1,11 @@ + +class: inverse, middle + +# Week `r rmarkdown::metadata$week`: .fancy[`r rmarkdown::metadata$title`] + +### 1. Intro to Quarto + +### QUIZ 1 + +### 2. Intro to ggplot2 +### 3. .orange[Project attributes & levels] diff --git a/sitemap.xml b/sitemap.xml index b1d1499..812d68f 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,170 +2,170 @@ https://madd.seas.gwu.edu/2024-Fall/syllabus.html - 2024-08-08T14:33:57.539Z + 2024-08-08T14:41:57.166Z https://madd.seas.gwu.edu/2024-Fall/project/6-final-analysis.html - 2024-08-08T14:33:57.537Z + 2024-08-08T14:41:57.165Z https://madd.seas.gwu.edu/2024-Fall/project/7-final-presentation.html - 2024-08-08T14:33:57.538Z + 2024-08-08T14:41:57.165Z https://madd.seas.gwu.edu/2024-Fall/project/3-pilot-survey.html - 2024-08-08T14:33:57.537Z + 2024-08-08T14:41:57.165Z https://madd.seas.gwu.edu/2024-Fall/index.html - 2024-08-08T14:33:57.536Z + 2024-08-08T14:41:57.164Z https://madd.seas.gwu.edu/2024-Fall/class/9-uncertainty.html - 2024-08-08T14:33:57.502Z + 2024-08-08T14:41:57.128Z https://madd.seas.gwu.edu/2024-Fall/class/7-utility-models.html - 2024-08-08T14:33:57.502Z + 2024-08-08T14:41:57.128Z https://madd.seas.gwu.edu/2024-Fall/class/4-intro-to-formr.html - 2024-08-08T14:33:57.502Z + 2024-08-08T14:41:57.128Z https://madd.seas.gwu.edu/2024-Fall/class/2-data-wrangling.html - 2024-08-08T14:33:57.502Z + 2024-08-08T14:41:57.044Z https://madd.seas.gwu.edu/2024-Fall/class/14-story-telling.html - 2024-08-08T14:33:57.501Z + 2024-08-08T14:41:57.044Z https://madd.seas.gwu.edu/2024-Fall/class/12-heterogeneity.html - 2024-08-08T14:33:57.501Z + 2024-08-08T14:41:57.043Z https://madd.seas.gwu.edu/2024-Fall/class/10-doe-power-analysis.html - 2024-08-08T14:33:57.501Z + 2024-08-08T14:41:57.043Z https://madd.seas.gwu.edu/2024-Fall/about.html - 2024-08-08T14:33:57.482Z + 2024-08-08T14:41:57.013Z https://madd.seas.gwu.edu/2024-Fall/references.html - 2024-08-08T14:33:57.538Z + 2024-08-08T14:41:57.165Z https://madd.seas.gwu.edu/2024-Fall/project/0-overview.html - 2024-08-08T14:33:57.536Z + 2024-08-08T14:41:57.164Z https://madd.seas.gwu.edu/2024-Fall/hw/8-doe-power-analysis.html - 2024-08-08T14:33:57.523Z + 2024-08-08T14:41:57.151Z https://madd.seas.gwu.edu/2024-Fall/hw/6-optimization-mle.html - 2024-08-08T14:33:57.523Z + 2024-08-08T14:41:57.151Z https://madd.seas.gwu.edu/2024-Fall/hw/4-conjoint-questions.html - 2024-08-08T14:33:57.523Z + 2024-08-08T14:41:57.151Z https://madd.seas.gwu.edu/2024-Fall/hw/2-quarto-plotting.html - 2024-08-08T14:33:57.522Z + 2024-08-08T14:41:57.151Z https://madd.seas.gwu.edu/2024-Fall/hw/10-heterogeneity.html - 2024-08-08T14:33:57.522Z + 2024-08-08T14:41:57.151Z https://madd.seas.gwu.edu/2024-Fall/software.html - 2024-08-08T14:33:57.539Z + 2024-08-08T14:41:57.166Z https://madd.seas.gwu.edu/2024-Fall/LICENSE.html - 2024-08-08T14:33:57.481Z + 2024-08-08T14:41:57.012Z https://madd.seas.gwu.edu/2024-Fall/hw/11-exam.html - 2024-08-08T14:33:57.522Z + 2024-08-08T14:41:57.151Z https://madd.seas.gwu.edu/2024-Fall/hw/3-intro-to-formr.html - 2024-08-08T14:33:57.523Z + 2024-08-08T14:41:57.151Z https://madd.seas.gwu.edu/2024-Fall/hw/5-utility-models.html - 2024-08-08T14:33:57.523Z + 2024-08-08T14:41:57.151Z https://madd.seas.gwu.edu/2024-Fall/hw/7-uncertainty.html - 2024-08-08T14:33:57.523Z + 2024-08-08T14:41:57.151Z https://madd.seas.gwu.edu/2024-Fall/hw/9-wtp-simulation.html - 2024-08-08T14:33:57.523Z + 2024-08-08T14:41:57.151Z https://madd.seas.gwu.edu/2024-Fall/project/examples.html - 2024-08-08T14:33:57.538Z + 2024-08-08T14:41:57.165Z https://madd.seas.gwu.edu/2024-Fall/self-assessment.html - 2024-08-08T14:33:57.539Z + 2024-08-08T14:41:57.166Z https://madd.seas.gwu.edu/2024-Fall/class/1-getting-started.html - 2024-08-08T14:33:57.482Z + 2024-08-08T14:41:57.013Z https://madd.seas.gwu.edu/2024-Fall/class/11-wtp-simulation.html - 2024-08-08T14:33:57.501Z + 2024-08-08T14:41:57.043Z https://madd.seas.gwu.edu/2024-Fall/class/13-class-review.html - 2024-08-08T14:33:57.501Z + 2024-08-08T14:41:57.043Z https://madd.seas.gwu.edu/2024-Fall/class/15-final-presentations.html - 2024-08-08T14:33:57.501Z + 2024-08-08T14:41:57.044Z https://madd.seas.gwu.edu/2024-Fall/class/3-quarto-plotting.html - 2024-08-08T14:33:57.502Z + 2024-08-08T14:41:57.078Z https://madd.seas.gwu.edu/2024-Fall/class/6-conjoint-questions.html - 2024-08-08T14:33:57.502Z + 2024-08-08T14:41:57.128Z https://madd.seas.gwu.edu/2024-Fall/class/8-optimization-mle.html - 2024-08-08T14:33:57.502Z + 2024-08-08T14:41:57.128Z https://madd.seas.gwu.edu/2024-Fall/schedule.html - 2024-08-08T14:33:57.539Z + 2024-08-08T14:41:57.166Z https://madd.seas.gwu.edu/2024-Fall/hw/1-data-wrangling.html - 2024-08-08T14:33:57.522Z + 2024-08-08T14:41:57.151Z https://madd.seas.gwu.edu/2024-Fall/project/2-survey-plan.html - 2024-08-08T14:33:57.536Z + 2024-08-08T14:41:57.164Z https://madd.seas.gwu.edu/2024-Fall/project/5-final-survey.html - 2024-08-08T14:33:57.537Z + 2024-08-08T14:41:57.165Z https://madd.seas.gwu.edu/2024-Fall/project/1-proposal.html - 2024-08-08T14:33:57.536Z + 2024-08-08T14:41:57.164Z https://madd.seas.gwu.edu/2024-Fall/project/4-pilot-analysis.html - 2024-08-08T14:33:57.537Z + 2024-08-08T14:41:57.165Z