diff --git a/.Rbuildignore b/.Rbuildignore index a80e032c..10f345fc 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -21,6 +21,7 @@ vignettes/ ^cran-comments\.md$ ^CRAN-SUBMISSION$ inst/user2025 +inst/Psychoco2026 #inst/tinytest/ Makefile ^.devcontainer diff --git a/inst/Psychoco2026/README.md b/inst/Psychoco2026/README.md new file mode 100644 index 00000000..96b9b84e --- /dev/null +++ b/inst/Psychoco2026/README.md @@ -0,0 +1,41 @@ +## tinyplot: Lightweight Extension of the Base R Graphics System + +_Grant McDermott, Vincent Arel-Bundock, Achim Zeileis_ + +**Presented at:** Psychoco 2026 - International Workshop on Psychometric Computing, Università di Padova, Italy, February 5-6. + +**PDF slides:** + +**Abstract:** + +The base R graphics system provides a lot of powerful infrastructure for drawing +data visualizations. At the core is the `plot()` generic function with its +default and formula methods. The default method can handle many basic plotting +elements (points, lines, etc.) and the formula method flexibly handles various +`y ~ x` setups including scatterplots (numeric `y` vs. numeric `x`), boxplots +(numeric `y` vs. categorical `x`), and spineplots/spinograms (categorical `y`). +Moreover, there are many elements that can be added like legends, axes, +annotation, grids of displays, etc. + +However, based on this powerful infrastructure base R provides only rather +limited convenience features such as those pioneered by newer (`grid`-based) +visualization packages like `ggplot2` and `lattice`, e.g., grouped plots with +automatic legends and/or facets, advanced visualization types, and easy +customization via ready-made themes. + +The `tinyplot` package fills this gap by providing a lightweight extension of +the base R graphics system. It aims to preserve the strengths of the base R +infrastructure (including the formula-based interface) while adding the +convenience features above without requiring (strong) non-base dependencies. +The presentation provides an introduction to `tinyplot` using various +visualization examples, highlighting strengths and weaknesses compared to other +packages. The package is available from CRAN +() and has many more galleries +and tutorials at . + +**Notes:** + +- The source file for the PDF slides is `slides-beamer.qmd`. This requires that + the [UIBK beamer class](https://git.uibk.ac.at/uibklatex/beamer_letter) is installed. +- As an alternative a similar HTML version of the slides can be created from + `slides-revealjs.qmd`. diff --git a/inst/Psychoco2026/_extensions/grantmcdermott/clean/_extension.yml b/inst/Psychoco2026/_extensions/grantmcdermott/clean/_extension.yml new file mode 100644 index 00000000..d45b0831 --- /dev/null +++ b/inst/Psychoco2026/_extensions/grantmcdermott/clean/_extension.yml @@ -0,0 +1,13 @@ +title: clean +author: Grant McDermott +version: 1.3.0 +quarto-required: ">=1.3.0" +contributes: + formats: + revealjs: + theme: [default, clean.scss] + menu: + side: left + slide-number: true + date-format: long + diff --git a/inst/Psychoco2026/_extensions/grantmcdermott/clean/clean.scss b/inst/Psychoco2026/_extensions/grantmcdermott/clean/clean.scss new file mode 100644 index 00000000..910d3021 --- /dev/null +++ b/inst/Psychoco2026/_extensions/grantmcdermott/clean/clean.scss @@ -0,0 +1,366 @@ +/*-- scss:defaults --*/ + +// Custom colours and variables + +$jet: #131516; +$accent: #5195a3; +$accent2: #f51459; +// $accent2: #e64173; +$right-arrow: "\2192"; // Unicode character for right arrow + +// fonts + +/* +Note: This theme uses the Roboto font family, which it imports from Google + Fonts to ensure consistent weighting in addition to availability. While + you can use a local installation of Roboto, this is generally not + recommended since the weighting will likely be wrong (probably too + light). OTOH, importing from Google Fonts can cause some issues in + certain secure environments due the external CDN (see: + https://github.com/grantmcdermott/quarto-revealjs-clean/issues/7). If + that's the case for you, simply comment out the `@import url(...)` line + below and it will default for the default Sans Serif font on your system + (e.g., Helvetica on a Mac). Circling back to the earlier point about + preserving consistent font weights, you may also wish to remove "Roboto" + from the choice set if the family is installed locally. +*/ +@import url('https://fonts.googleapis.com/css?family=Roboto:200,200i,300,300i,350,350i,400,400i&display=swap'); + +$font-family-sans-serif: "Roboto", sans-serif !default; +$presentation-heading-font: "Roboto", sans-serif !default; + +$presentation-heading-color: $jet !default; +$presentation-heading-font-weight: lighter; +//$presentation-heading-line-height: 2; +//$presentation-block-margin: 28px; +$presentation-font-size-root: 32px; + +// colors +//$body-bg: #f0f1eb !default; +$body-color: $jet !default; +$link-color: $accent !default; +$selection-bg: #26351c !default; + + +/*-- scss:rules --*/ + +.reveal a { + line-height: 1.5em; +} + +.reveal p { + // font-weight: 300; + font-weight: lighter; + margin-top: 1.25em; +} + +// title and headings + +#title-slide { + text-align: left; + + .title { + color: $body-color; + font-size: 1.4em; + // font-weight: 350; + font-weight: lighter; + } + + .subtitle { + color: $accent; + font-style: italic; + margin-top: 0em; + font-weight: lighter; + } + + .institute, + .quarto-title-affiliation, + .quarto-title-author-email { + font-style: italic; + // font-size: 80%; + // color: #7F7F7F; + } + + .author, + .quarto-title-author-name { + color: $body-color; + } + + .quarto-title-authors { + display: flex; + justify-content: left; + + .quarto-title-author { + padding-left: 0em; + padding-right: 0em; + width: 100%; + } + } + +} + + +.reveal h2 { + // font-weight: 350; + font-weight: lighter; + font-size: 1.4em; +} + +.reveal h3 { + color: $accent; + font-style: italic; + // font-weight: 350; + font-weight: lighter; + font-size: 0.95em; +} + +.reveal h4 { + color: $accent2; + // font-weight: 350; + font-weight: normal; + margin-top: 1.25em; +} + +// alerts etc. + +.alert { + color: $accent2; +} + +.fg { + color: var(--col, $jet); +} + +.bg { + background-color: var(--col, #fff); + padding: 0.1em; + border-radius: 5px; + display: inline-block; +} + +// lists + +// Unordered lists + +.reveal ul { + // font-weight: 300; + font-weight: lighter; + padding-left: 16px; + + li::marker { + color: mix($accent, white, 70%); + } +} + +.reveal ul ul { + list-style: none; + + li:before { + content: $right-arrow; + color: mix($accent, white, 60%); + display: inline-block; + width: 1em; + margin-left: -1em; + margin-right: 0.5em; + } +} + +// Ordered lists + +.reveal ol { + // font-weight: 300; + font-weight: lighter; + padding-left: 16px; + + li::marker { + color: $accent; + } +} + +// Move "hamburger" menu button to top right + +.reveal .slide-menu-button { + position: fixed; + top: 6px; + right: 0; + display: flex; + justify-content: flex-end; + align-items: flex-start; + pointer-events: none; +} + +.reveal .slide-menu-button > * { + pointer-events: auto; +} + +// Same for chalkboard buttons (with an offset) + +.reveal .slide-chalkboard-buttons { + position: fixed; + top: 12px; + right: 24px; + display: flex; + justify-content: flex-end; + align-items: flex-start; + pointer-events: none; +} + +.reveal .slide-chalkboard-buttons > * { + pointer-events: auto; +} + +// Logo to the bottom-left +.slide-logo { + display: block !important; + position: fixed !important; + bottom: 0 !important; + left: 10px !important; + // max-width: 150px; // Adjust if necessary + // max-height: 50px; + // width: auto !important; + height: 150px !important; + // width: 150px !important; + max-width: unset !important; + max-height: unset !important; + color: $body-color !important; +} + +// Also need to enforce slide numbers at bottom-right (if logo is present) +.slide-number, .reveal.has-logo .slide-number { + bottom: 6px !important; + right: 10px !important; + top: unset !important; + color: #777777 !important; +} + +// Beamer-style button link environment + +.button { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: 400; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + background-color: $accent; + border: 1px solid $accent; + color: #fff !important; + text-decoration: none; + border-radius: 4px; + transition: all 0.2s ease-in-out; +} + +.button:hover { + background-color: #0056b3; + border-color: #0056b3; +} + +.button::before { + content: "▶"; + margin-right: 5px; +} + +// tables + +.reveal table { + // height: auto; /* Adjust table width to fit content up to the available slide space */ + margin: auto; + border-collapse: collapse; + border-spacing: 0; + font-size: 0.8em; +} + +.reveal table th, +.reveal table td { + border: none; /* Remove internal row lines */ + padding: .23em .46em; /* Adjust padding as needed */ + text-align: left; /* Adjust text alignment as needed */ + font-weight: lighter; /* Lighter font weight for main table text */ +} + +/* Add top border to first row and bottom border to last row only */ +.reveal table thead th, +.reveal .slides table tr:first-child td { + border-top: 2px solid #D3D3D3; +} + +.reveal table thead th, +.reveal .slides table tr:last-child td, +.reveal .slides table { + border-bottom: 2px solid #D3D3D3; +} + +/* Make column headers bold */ +.reveal table thead th { + font-weight: bold; +} + +/* Styling table captions */ +.reveal table caption { + color: #666666; /* Dark grey color for the caption */ + font-variant: small-caps; /* Use small caps for the caption text */ +} + +// Special catch for etable environment to ensure these table images +// don't overflow the slide. +// See: https://lrberge.github.io/fixest/articles/etable_new_features.html + +.etable { + width: 100%; + height: calc(100% - 3em); /* Adjust 3em based on the height of your header, if necessary */ + display: flex; + align-items: center; + justify-content: center; +} + +.etable img { + max-width: 100%; + max-height: 100%; + width: auto; + height: auto; + object-fit: contain; +} + +// Change the relative widths of `output-location: column`. +// See: https://github.com/grantmcdermott/quarto-revealjs-clean/pull/16 +// Example usage: +// ```{python} +// #| echo: true +// #| output-location: column +// #| classes: columns3070 +// +// ``` +.reveal .columns3070 > div.column:first-child { + width: 30%; +} +.reveal .columns3070 div.column:not(:first-child) { + width: 70%; +} +.reveal .columns7030 > div.column:first-child { + width: 70%; +} +.reveal .columns7030 div.column:not(:first-child) { + width: 30%; +} +.reveal .columns4060 > div.column:first-child { + width: 40%; +} +.reveal .columns4060 div.column:not(:first-child) { + width: 60%; +} +.reveal .columns6040 > div.column:first-child { + width: 60%; +} +.reveal .columns6040 div.column:not(:first-child) { + width: 40%; +} + +// // inline title fig (https://stackoverflow.com/a/74100740/4115816) +// .center{ +// margin: 0!important; +// height: 1.4em; +// } \ No newline at end of file diff --git a/inst/Psychoco2026/_extensions/mcanouil/iconify/LICENSE b/inst/Psychoco2026/_extensions/mcanouil/iconify/LICENSE new file mode 100644 index 00000000..43fb0c20 --- /dev/null +++ b/inst/Psychoco2026/_extensions/mcanouil/iconify/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Mickaël Canouil + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/inst/Psychoco2026/_extensions/mcanouil/iconify/_extension.yml b/inst/Psychoco2026/_extensions/mcanouil/iconify/_extension.yml new file mode 100644 index 00000000..4222542e --- /dev/null +++ b/inst/Psychoco2026/_extensions/mcanouil/iconify/_extension.yml @@ -0,0 +1,7 @@ +title: Iconify +author: Mickaël Canouil +version: 3.0.1 +quarto-required: ">=1.5.57" +contributes: + shortcodes: + - iconify.lua diff --git a/inst/Psychoco2026/_extensions/mcanouil/iconify/iconify-icon.min.js b/inst/Psychoco2026/_extensions/mcanouil/iconify/iconify-icon.min.js new file mode 100644 index 00000000..00cee269 --- /dev/null +++ b/inst/Psychoco2026/_extensions/mcanouil/iconify/iconify-icon.min.js @@ -0,0 +1,13 @@ +/** +* (c) Iconify +* +* For the full copyright and license information, please view the license.txt +* files at https://github.com/iconify/iconify +* +* Licensed under MIT. +* Source: https://github.com/iconify/code/tree/gh-pages/iconify-icon +* +* @license MIT +* @version 3.0.0 +*/ +!function(){"use strict";const t=Object.freeze({left:0,top:0,width:16,height:16}),e=Object.freeze({rotate:0,vFlip:!1,hFlip:!1}),n=Object.freeze({...t,...e}),i=Object.freeze({...n,body:"",hidden:!1}),r=Object.freeze({width:null,height:null}),o=Object.freeze({...r,...e});const s=/[\s,]+/;const c={...o,preserveAspectRatio:""};function a(t){const e={...c},n=(e,n)=>t.getAttribute(e)||n;var i;return e.width=n("width",null),e.height=n("height",null),e.rotate=function(t,e=0){const n=t.replace(/^-?[0-9.]*/,"");function i(t){for(;t<0;)t+=4;return t%4}if(""===n){const e=parseInt(t);return isNaN(e)?0:i(e)}if(n!==t){let e=0;switch(n){case"%":e=25;break;case"deg":e=90}if(e){let r=parseFloat(t.slice(0,t.length-n.length));return isNaN(r)?0:(r/=e,r%1==0?i(r):0)}}return e}(n("rotate","")),i=e,n("flip","").split(s).forEach((t=>{switch(t.trim()){case"horizontal":i.hFlip=!0;break;case"vertical":i.vFlip=!0}})),e.preserveAspectRatio=n("preserveAspectRatio",n("preserveaspectratio","")),e}const u=/^[a-z0-9]+(-[a-z0-9]+)*$/,l=(t,e,n,i="")=>{const r=t.split(":");if("@"===t.slice(0,1)){if(r.length<2||r.length>3)return null;i=r.shift().slice(1)}if(r.length>3||!r.length)return null;if(r.length>1){const t=r.pop(),n=r.pop(),o={provider:r.length>0?r[0]:i,prefix:n,name:t};return e&&!f(o)?null:o}const o=r[0],s=o.split("-");if(s.length>1){const t={provider:i,prefix:s.shift(),name:s.join("-")};return e&&!f(t)?null:t}if(n&&""===i){const t={provider:i,prefix:"",name:o};return e&&!f(t,n)?null:t}return null},f=(t,e)=>!!t&&!(!(e&&""===t.prefix||t.prefix)||!t.name);function d(t,n){const r=function(t,e){const n={};!t.hFlip!=!e.hFlip&&(n.hFlip=!0),!t.vFlip!=!e.vFlip&&(n.vFlip=!0);const i=((t.rotate||0)+(e.rotate||0))%4;return i&&(n.rotate=i),n}(t,n);for(const o in i)o in e?o in t&&!(o in r)&&(r[o]=e[o]):o in n?r[o]=n[o]:o in t&&(r[o]=t[o]);return r}function h(t,e,n){const i=t.icons,r=t.aliases||Object.create(null);let o={};function s(t){o=d(i[t]||r[t],o)}return s(e),n.forEach(s),d(t,o)}function p(t,e){const n=[];if("object"!=typeof t||"object"!=typeof t.icons)return n;t.not_found instanceof Array&&t.not_found.forEach((t=>{e(t,null),n.push(t)}));const i=function(t,e){const n=t.icons,i=t.aliases||Object.create(null),r=Object.create(null);return Object.keys(n).concat(Object.keys(i)).forEach((function t(e){if(n[e])return r[e]=[];if(!(e in r)){r[e]=null;const n=i[e]&&i[e].parent,o=n&&t(n);o&&(r[e]=[n].concat(o))}return r[e]})),r}(t);for(const r in i){const o=i[r];o&&(e(r,h(t,r,o)),n.push(r))}return n}const g={provider:"",aliases:{},not_found:{},...t};function b(t,e){for(const n in e)if(n in t&&typeof t[n]!=typeof e[n])return!1;return!0}function v(t){if("object"!=typeof t||null===t)return null;const e=t;if("string"!=typeof e.prefix||!t.icons||"object"!=typeof t.icons)return null;if(!b(t,g))return null;const n=e.icons;for(const t in n){const e=n[t];if(!t||"string"!=typeof e.body||!b(e,i))return null}const r=e.aliases||Object.create(null);for(const t in r){const e=r[t],o=e.parent;if(!t||"string"!=typeof o||!n[o]&&!r[o]||!b(e,i))return null}return e}const m=Object.create(null);function y(t,e){const n=m[t]||(m[t]=Object.create(null));return n[e]||(n[e]=function(t,e){return{provider:t,prefix:e,icons:Object.create(null),missing:new Set}}(t,e))}function x(t,e){return v(e)?p(e,((e,n)=>{n?t.icons[e]=n:t.missing.add(e)})):[]}function _(t,e){let n=[];return("string"==typeof t?[t]:Object.keys(m)).forEach((t=>{("string"==typeof t&&"string"==typeof e?[e]:Object.keys(m[t]||{})).forEach((e=>{const i=y(t,e);n=n.concat(Object.keys(i.icons).map((n=>(""!==t?"@"+t+":":"")+e+":"+n)))}))})),n}let w=!1;function k(t){return"boolean"==typeof t&&(w=t),w}function A(t){const e="string"==typeof t?l(t,!0,w):t;if(e){const t=y(e.provider,e.prefix),n=e.name;return t.icons[n]||(t.missing.has(n)?null:void 0)}}function j(t,e){const n=l(t,!0,w);if(!n)return!1;const i=y(n.provider,n.prefix);return e?function(t,e,n){try{if("string"==typeof n.body)return t.icons[e]={...n},!0}catch(t){}return!1}(i,n.name,e):(i.missing.add(n.name),!0)}function O(t,e){if("object"!=typeof t)return!1;if("string"!=typeof e&&(e=t.provider||""),w&&!e&&!t.prefix){let e=!1;return v(t)&&(t.prefix="",p(t,((t,n)=>{j(t,n)&&(e=!0)}))),e}const n=t.prefix;if(!f({prefix:n,name:"a"}))return!1;return!!x(y(e,n),t)}function C(t){return!!A(t)}function I(t){const e=A(t);return e?{...n,...e}:e}function E(t,e){t.forEach((t=>{const n=t.loaderCallbacks;n&&(t.loaderCallbacks=n.filter((t=>t.id!==e)))}))}let T=0;const F=Object.create(null);function R(t,e){F[t]=e}function S(t){return F[t]||F[""]}var L={resources:[],index:0,timeout:2e3,rotate:750,random:!1,dataAfterTimeout:!1};function P(t,e,n,i){const r=t.resources.length,o=t.random?Math.floor(Math.random()*r):t.index;let s;if(t.random){let e=t.resources.slice(0);for(s=[];e.length>1;){const t=Math.floor(Math.random()*e.length);s.push(e[t]),e=e.slice(0,t).concat(e.slice(t+1))}s=s.concat(e)}else s=t.resources.slice(o).concat(t.resources.slice(0,o));const c=Date.now();let a,u="pending",l=0,f=null,d=[],h=[];function p(){f&&(clearTimeout(f),f=null)}function g(){"pending"===u&&(u="aborted"),p(),d.forEach((t=>{"pending"===t.status&&(t.status="aborted")})),d=[]}function b(t,e){e&&(h=[]),"function"==typeof t&&h.push(t)}function v(){u="failed",h.forEach((t=>{t(void 0,a)}))}function m(){d.forEach((t=>{"pending"===t.status&&(t.status="aborted")})),d=[]}function y(){if("pending"!==u)return;p();const i=s.shift();if(void 0===i)return d.length?void(f=setTimeout((()=>{p(),"pending"===u&&(m(),v())}),t.timeout)):void v();const r={status:"pending",resource:i,callback:(e,n)=>{!function(e,n,i){const r="success"!==n;switch(d=d.filter((t=>t!==e)),u){case"pending":break;case"failed":if(r||!t.dataAfterTimeout)return;break;default:return}if("abort"===n)return a=i,void v();if(r)return a=i,void(d.length||(s.length?y():v()));if(p(),m(),!t.random){const n=t.resources.indexOf(e.resource);-1!==n&&n!==t.index&&(t.index=n)}u="completed",h.forEach((t=>{t(i)}))}(r,e,n)}};d.push(r),l++,f=setTimeout(y,t.rotate),n(i,e,r.callback)}return"function"==typeof i&&h.push(i),setTimeout(y),function(){return{startTime:c,payload:e,status:u,queriesSent:l,queriesPending:d.length,subscribe:b,abort:g}}}function M(t){const e={...L,...t};let n=[];function i(){n=n.filter((t=>"pending"===t().status))}return{query:function(t,r,o){const s=P(e,t,r,((t,e)=>{i(),o&&o(t,e)}));return n.push(s),s},find:function(t){return n.find((e=>t(e)))||null},setIndex:t=>{e.index=t},getIndex:()=>e.index,cleanup:i}}function N(t){let e;if("string"==typeof t.resources)e=[t.resources];else if(e=t.resources,!(e instanceof Array&&e.length))return null;return{resources:e,path:t.path||"/",maxURL:t.maxURL||500,rotate:t.rotate||750,timeout:t.timeout||5e3,random:!0===t.random,index:t.index||0,dataAfterTimeout:!1!==t.dataAfterTimeout}}const z=Object.create(null),Q=["https://api.simplesvg.com","https://api.unisvg.com"],q=[];for(;Q.length>0;)1===Q.length||Math.random()>.5?q.push(Q.shift()):q.push(Q.pop());function U(t,e){const n=N(e);return null!==n&&(z[t]=n,!0)}function D(t){return z[t]}function H(){return Object.keys(z)}function J(){}z[""]=N({resources:["https://api.iconify.design"].concat(q)});const $=Object.create(null);function B(t,e,n){let i,r;if("string"==typeof t){const e=S(t);if(!e)return n(void 0,424),J;r=e.send;const o=function(t){if(!$[t]){const e=D(t);if(!e)return;const n={config:e,redundancy:M(e)};$[t]=n}return $[t]}(t);o&&(i=o.redundancy)}else{const e=N(t);if(e){i=M(e);const n=S(t.resources?t.resources[0]:"");n&&(r=n.send)}}return i&&r?i.query(e,r,n)().abort:(n(void 0,424),J)}function G(){}function V(t){t.iconsLoaderFlag||(t.iconsLoaderFlag=!0,setTimeout((()=>{t.iconsLoaderFlag=!1,function(t){t.pendingCallbacksFlag||(t.pendingCallbacksFlag=!0,setTimeout((()=>{t.pendingCallbacksFlag=!1;const e=t.loaderCallbacks?t.loaderCallbacks.slice(0):[];if(!e.length)return;let n=!1;const i=t.provider,r=t.prefix;e.forEach((e=>{const o=e.icons,s=o.pending.length;o.pending=o.pending.filter((e=>{if(e.prefix!==r)return!0;const s=e.name;if(t.icons[s])o.loaded.push({provider:i,prefix:r,name:s});else{if(!t.missing.has(s))return n=!0,!0;o.missing.push({provider:i,prefix:r,name:s})}return!1})),o.pending.length!==s&&(n||E([t],e.id),e.callback(o.loaded.slice(0),o.missing.slice(0),o.pending.slice(0),e.abort))}))})))}(t)})))}function K(t,e,n){function i(){const n=t.pendingIcons;e.forEach((e=>{n&&n.delete(e),t.icons[e]||t.missing.add(e)}))}if(n&&"object"==typeof n)try{if(!x(t,n).length)return void i()}catch(t){console.error(t)}i(),V(t)}function W(t,e){t instanceof Promise?t.then((t=>{e(t)})).catch((()=>{e(null)})):e(t)}function X(t,e){t.iconsToLoad?t.iconsToLoad=t.iconsToLoad.concat(e).sort():t.iconsToLoad=e,t.iconsQueueFlag||(t.iconsQueueFlag=!0,setTimeout((()=>{t.iconsQueueFlag=!1;const{provider:e,prefix:n}=t,i=t.iconsToLoad;if(delete t.iconsToLoad,!i||!i.length)return;const r=t.loadIcon;if(t.loadIcons&&(i.length>1||!r))return void W(t.loadIcons(i,n,e),(e=>{K(t,i,e)}));if(r)return void i.forEach((i=>{W(r(i,n,e),(e=>{K(t,[i],e?{prefix:n,icons:{[i]:e}}:null)}))}));const{valid:o,invalid:s}=function(t){const e=[],n=[];return t.forEach((t=>{(t.match(u)?e:n).push(t)})),{valid:e,invalid:n}}(i);if(s.length&&K(t,s,null),!o.length)return;const c=n.match(u)?S(e):null;if(!c)return void K(t,o,null);c.prepare(e,n,o).forEach((n=>{B(e,n,(e=>{K(t,n.icons,e)}))}))})))}const Y=(t,e)=>{const n=function(t,e=!0,n=!1){const i=[];return t.forEach((t=>{const r="string"==typeof t?l(t,e,n):t;r&&i.push(r)})),i}(t,!0,k()),i=function(t){const e={loaded:[],missing:[],pending:[]},n=Object.create(null);t.sort(((t,e)=>t.provider!==e.provider?t.provider.localeCompare(e.provider):t.prefix!==e.prefix?t.prefix.localeCompare(e.prefix):t.name.localeCompare(e.name)));let i={provider:"",prefix:"",name:""};return t.forEach((t=>{if(i.name===t.name&&i.prefix===t.prefix&&i.provider===t.provider)return;i=t;const r=t.provider,o=t.prefix,s=t.name,c=n[r]||(n[r]=Object.create(null)),a=c[o]||(c[o]=y(r,o));let u;u=s in a.icons?e.loaded:""===o||a.missing.has(s)?e.missing:e.pending;const l={provider:r,prefix:o,name:s};u.push(l)})),e}(n);if(!i.pending.length){let t=!0;return e&&setTimeout((()=>{t&&e(i.loaded,i.missing,i.pending,G)})),()=>{t=!1}}const r=Object.create(null),o=[];let s,c;return i.pending.forEach((t=>{const{provider:e,prefix:n}=t;if(n===c&&e===s)return;s=e,c=n,o.push(y(e,n));const i=r[e]||(r[e]=Object.create(null));i[n]||(i[n]=[])})),i.pending.forEach((t=>{const{provider:e,prefix:n,name:i}=t,o=y(e,n),s=o.pendingIcons||(o.pendingIcons=new Set);s.has(i)||(s.add(i),r[e][n].push(i))})),o.forEach((t=>{const e=r[t.provider][t.prefix];e.length&&X(t,e)})),e?function(t,e,n){const i=T++,r=E.bind(null,n,i);if(!e.pending.length)return r;const o={id:i,icons:e,callback:t,abort:r};return n.forEach((t=>{(t.loaderCallbacks||(t.loaderCallbacks=[])).push(o)})),r}(e,i,o):G},Z=t=>new Promise(((e,i)=>{const r="string"==typeof t?l(t,!0):t;r?Y([r||t],(o=>{if(o.length&&r){const t=A(r);if(t)return void e({...n,...t})}i(t)})):i(t)}));function tt(t){try{const e="string"==typeof t?JSON.parse(t):t;if("string"==typeof e.body)return{...e}}catch(t){}}let et=!1;try{et=0===navigator.vendor.indexOf("Apple")}catch(t){}const nt=/(-?[0-9.]*[0-9]+[0-9.]*)/g,it=/^-?[0-9.]*[0-9]+[0-9.]*$/g;function rt(t,e,n){if(1===e)return t;if(n=n||100,"number"==typeof t)return Math.ceil(t*e*n)/n;if("string"!=typeof t)return t;const i=t.split(nt);if(null===i||!i.length)return t;const r=[];let o=i.shift(),s=it.test(o);for(;;){if(s){const t=parseFloat(o);isNaN(t)?r.push(o):r.push(Math.ceil(t*e*n)/n)}else r.push(o);if(o=i.shift(),void 0===o)return r.join("");s=!s}}const ot=t=>"unset"===t||"undefined"===t||"none"===t;function st(t,e){const i={...n,...t},r={...o,...e},s={left:i.left,top:i.top,width:i.width,height:i.height};let c=i.body;[i,r].forEach((t=>{const e=[],n=t.hFlip,i=t.vFlip;let r,o=t.rotate;switch(n?i?o+=2:(e.push("translate("+(s.width+s.left).toString()+" "+(0-s.top).toString()+")"),e.push("scale(-1 1)"),s.top=s.left=0):i&&(e.push("translate("+(0-s.left).toString()+" "+(s.height+s.top).toString()+")"),e.push("scale(1 -1)"),s.top=s.left=0),o<0&&(o-=4*Math.floor(o/4)),o%=4,o){case 1:r=s.height/2+s.top,e.unshift("rotate(90 "+r.toString()+" "+r.toString()+")");break;case 2:e.unshift("rotate(180 "+(s.width/2+s.left).toString()+" "+(s.height/2+s.top).toString()+")");break;case 3:r=s.width/2+s.left,e.unshift("rotate(-90 "+r.toString()+" "+r.toString()+")")}o%2==1&&(s.left!==s.top&&(r=s.left,s.left=s.top,s.top=r),s.width!==s.height&&(r=s.width,s.width=s.height,s.height=r)),e.length&&(c=function(t,e,n){const i=function(t,e="defs"){let n="";const i=t.indexOf("<"+e);for(;i>=0;){const r=t.indexOf(">",i),o=t.indexOf("",o);if(-1===s)break;n+=t.slice(r+1,o).trim(),t=t.slice(0,i).trim()+t.slice(s+1)}return{defs:n,content:t}}(t);return r=i.defs,o=e+i.content+n,r?""+r+""+o:o;var r,o}(c,'',""))}));const a=r.width,u=r.height,l=s.width,f=s.height;let d,h;null===a?(h=null===u?"1em":"auto"===u?f:u,d=rt(h,l/f)):(d="auto"===a?l:a,h=null===u?rt(d,f/l):"auto"===u?f:u);const p={},g=(t,e)=>{ot(e)||(p[t]=e.toString())};g("width",d),g("height",h);const b=[s.left,s.top,l,f];return p.viewBox=b.join(" "),{attributes:p,viewBox:b,body:c}}function ct(t,e){let n=-1===t.indexOf("xlink:")?"":' xmlns:xlink="http://www.w3.org/1999/xlink"';for(const t in e)n+=" "+t+'="'+e[t]+'"';return'"+t+""}function at(t){return'url("'+function(t){return"data:image/svg+xml,"+function(t){return t.replace(/"/g,"'").replace(/%/g,"%25").replace(/#/g,"%23").replace(//g,"%3E").replace(/\s+/g," ")}(t)}(t)+'")'}let ut=(()=>{let t;try{if(t=fetch,"function"==typeof t)return t}catch(t){}})();function lt(t){ut=t}function ft(){return ut}const dt={prepare:(t,e,n)=>{const i=[],r=function(t,e){const n=D(t);if(!n)return 0;let i;if(n.maxURL){let t=0;n.resources.forEach((e=>{const n=e;t=Math.max(t,n.length)}));const r=e+".json?icons=";i=n.maxURL-t-n.path.length-r.length}else i=0;return i}(t,e),o="icons";let s={type:o,provider:t,prefix:e,icons:[]},c=0;return n.forEach(((n,a)=>{c+=n.length+1,c>=r&&a>0&&(i.push(s),s={type:o,provider:t,prefix:e,icons:[]},c=n.length),s.icons.push(n)})),i.push(s),i},send:(t,e,n)=>{if(!ut)return void n("abort",424);let i=function(t){if("string"==typeof t){const e=D(t);if(e)return e.path}return"/"}(e.provider);switch(e.type){case"icons":{const t=e.prefix,n=e.icons.join(",");i+=t+".json?"+new URLSearchParams({icons:n}).toString();break}case"custom":{const t=e.uri;i+="/"===t.slice(0,1)?t.slice(1):t;break}default:return void n("abort",400)}let r=503;ut(t+i).then((t=>{const e=t.status;if(200===e)return r=501,t.json();setTimeout((()=>{n(function(t){return 404===t}(e)?"abort":"next",e)}))})).then((t=>{"object"==typeof t&&null!==t?setTimeout((()=>{n("success",t)})):setTimeout((()=>{404===t?n("abort",t):n("next",r)}))})).catch((()=>{n("next",r)}))}};function ht(t,e,n){y(n||"",e).loadIcons=t}function pt(t,e,n){y(n||"",e).loadIcon=t}const gt="data-style";let bt="";function vt(t){bt=t}function mt(t,e){let n=Array.from(t.childNodes).find((t=>t.hasAttribute&&t.hasAttribute(gt)));n||(n=document.createElement("style"),n.setAttribute(gt,gt),t.appendChild(n)),n.textContent=":host{display:inline-block;vertical-align:"+(e?"-0.125em":"0")+"}span,svg{display:block;margin:auto}"+bt}const yt={"background-color":"currentColor"},xt={"background-color":"transparent"},_t={image:"var(--svg)",repeat:"no-repeat",size:"100% 100%"},wt={"-webkit-mask":yt,mask:yt,background:xt};for(const t in wt){const e=wt[t];for(const n in _t)e[t+"-"+n]=_t[n]}function kt(t){return t?t+(t.match(/^[-0-9.]+$/)?"px":""):"inherit"}let At;function jt(t){return void 0===At&&function(){try{At=window.trustedTypes.createPolicy("iconify",{createHTML:t=>t})}catch(t){At=null}}(),At?At.createHTML(t):t}function Ot(t){return Array.from(t.childNodes).find((t=>{const e=t.tagName&&t.tagName.toUpperCase();return"SPAN"===e||"SVG"===e}))}function Ct(t,e){const i=e.icon.data,r=e.customisations,o=st(i,r);r.preserveAspectRatio&&(o.attributes.preserveAspectRatio=r.preserveAspectRatio);const s=e.renderedMode;let c;if("svg"===s)c=function(t){const e=document.createElement("span"),n=t.attributes;let i="";n.width||(i="width: inherit;"),n.height||(i+="height: inherit;"),i&&(n.style=i);const r=ct(t.body,n);return e.innerHTML=jt(r),e.firstChild}(o);else c=function(t,e,n){const i=document.createElement("span");let r=t.body;-1!==r.indexOf("{this._check()})))}_check(){if(!this._checkQueued)return;this._checkQueued=!1;const t=this._state,e=this.getAttribute("icon");if(e!==t.icon.value)return void this._iconChanged(e);if(!t.rendered||!this._visible)return;const n=this.getAttribute("mode"),i=a(this);t.attrMode===n&&!function(t,e){for(const n in c)if(t[n]!==e[n])return!0;return!1}(t.customisations,i)&&Ot(this._shadowRoot)||this._renderIcon(t.icon,i,n)}_iconChanged(t){const e=function(t,e){if("object"==typeof t)return{data:tt(t),value:t};if("string"!=typeof t)return{value:t};if(t.includes("{")){const e=tt(t);if(e)return{data:e,value:t}}const n=l(t,!0,!0);if(!n)return{value:t};const i=A(n);if(void 0!==i||!n.prefix)return{value:t,name:n,data:i};const r=Y([n],(()=>e(t,n,A(n))));return{value:t,name:n,loading:r}}(t,((t,e,n)=>{const i=this._state;if(i.rendered||this.getAttribute("icon")!==t)return;const r={value:t,name:e,data:n};r.data?this._gotIconData(r):i.icon=r}));e.data?this._gotIconData(e):this._state=It(e,this._state.inline,this._state)}_forceRender(){if(this._visible)this._queueCheck();else{const t=Ot(this._shadowRoot);t&&this._shadowRoot.removeChild(t)}}_gotIconData(t){this._checkQueued=!1,this._renderIcon(t,a(this),this.getAttribute("mode"))}_renderIcon(t,e,n){const i=function(t,e){switch(e){case"svg":case"bg":case"mask":return e}return"style"===e||!et&&-1!==t.indexOf("{const e=t.some((t=>t.isIntersecting));e!==this._visible&&(this._visible=e,this._forceRender())})),this._observer.observe(this)}catch(t){if(this._observer){try{this._observer.disconnect()}catch(t){}this._observer=null}}}stopObserver(){this._observer&&(this._observer.disconnect(),this._observer=null,this._visible=!0,this._connected&&this._forceRender())}};r.forEach((t=>{t in o.prototype||Object.defineProperty(o.prototype,t,{get:function(){return this.getAttribute(t)},set:function(e){null!==e?this.setAttribute(t,e):this.removeAttribute(t)}})}));const s=function(){let t;R("",dt),k(!0);try{t=window}catch(t){}if(t){if(void 0!==t.IconifyPreload){const e=t.IconifyPreload,n="Invalid IconifyPreload syntax.";"object"==typeof e&&null!==e&&(e instanceof Array?e:[e]).forEach((t=>{try{("object"!=typeof t||null===t||t instanceof Array||"object"!=typeof t.icons||"string"!=typeof t.prefix||!O(t))&&console.error(n)}catch(t){console.error(n)}}))}if(void 0!==t.IconifyProviders){const e=t.IconifyProviders;if("object"==typeof e&&null!==e)for(const t in e){const n="IconifyProviders["+t+"] is invalid.";try{const i=e[t];if("object"!=typeof i||!i||void 0===i.resources)continue;U(t,i)||console.error(n)}catch(t){console.error(n)}}}}return{iconLoaded:C,getIcon:I,listIcons:_,addIcon:j,addCollection:O,calculateSize:rt,buildIcon:st,iconToHTML:ct,svgToURL:at,loadIcons:Y,loadIcon:Z,addAPIProvider:U,setCustomIconLoader:pt,setCustomIconsLoader:ht,appendCustomStyle:vt,_api:{getAPIConfig:D,setAPIModule:R,sendAPIQuery:B,setFetch:lt,getFetch:ft,listAPIProviders:H}}}();for(const t in s)o[t]=o.prototype[t]=s[t];e.define(t,o)}()}(); diff --git a/inst/Psychoco2026/_extensions/mcanouil/iconify/iconify.lua b/inst/Psychoco2026/_extensions/mcanouil/iconify/iconify.lua new file mode 100644 index 00000000..24bcc873 --- /dev/null +++ b/inst/Psychoco2026/_extensions/mcanouil/iconify/iconify.lua @@ -0,0 +1,300 @@ +--[[ +# MIT License +# +# Copyright (c) 2025 Mickaël Canouil +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +]] + +--- @type function +local stringify = pandoc.utils.stringify + +--- Flag to track if deprecation warning has been shown +--- @type boolean +local deprecation_warning_shown = false + +--- Ensure Iconify HTML dependencies are included. +--- @return nil +local function ensure_html_deps() + quarto.doc.add_html_dependency({ + name = 'iconify', + version = '3.0.0', + scripts = { 'iconify-icon.min.js' } + }) +end + +--- Check if a string is empty or nil. +--- @param s string|nil +--- @return boolean +local function is_empty(s) + return s == nil or s == '' +end + +--- Check for deprecated top-level iconify configuration and emit warning. +--- @param meta table Document metadata table +--- @param key string The configuration key being accessed +--- @return string|nil The value from deprecated config, or nil if not found +local function check_deprecated_config(meta, key) + if not is_empty(meta['iconify']) and not is_empty(meta['iconify'][key]) then + if not deprecation_warning_shown then + quarto.log.warning( + 'Top-level "iconify" configuration is deprecated. ' .. + 'Please use:\n' .. + 'extensions:\n' .. + ' iconify:\n' .. + ' ' .. key .. ': value' + ) + deprecation_warning_shown = true + end + return stringify(meta['iconify'][key]) + end + return nil +end + +--- Validate and convert size keyword to CSS font-size. +--- @param size string|nil +--- @return string +local function is_valid_size(size) + if is_empty(size) then + return '' + end + --- @type table + local size_table = { + ['tiny'] = '0.5em', + ['scriptsize'] = '0.7em', + ['footnotesize'] = '0.8em', + ['small'] = '0.9em', + ['normalsize'] = '1em', + ['large'] = '1.2em', + ['Large'] = '1.5em', + ['LARGE'] = '1.75em', + ['huge'] = '2em', + ['Huge'] = '2.5em', + ['1x'] = '1em', + ['2x'] = '2em', + ['3x'] = '3em', + ['4x'] = '4em', + ['5x'] = '5em', + ['6x'] = '6em', + ['7x'] = '7em', + ['8x'] = '8em', + ['9x'] = '9em', + ['10x'] = '10em', + ['2xs'] = '0.625em', + ['xs'] = '0.75em', + ['sm'] = '0.875em', + ['lg'] = '1.25em', + ['xl'] = '1.5em', + ['2xl'] = '2em' + } + for key, value in pairs(size_table) do + if key == size then + return 'font-size: ' .. value .. ';' + end + end + return 'font-size: ' .. size .. ';' +end + +--- Get iconify option from arguments or metadata. +--- @param x string The option name to retrieve +--- @param arg table Arguments table containing options +--- @param meta table Document metadata table +--- @return string The option value as a string +local function get_iconify_options(x, arg, meta) + --- @type string + local arg_value = stringify(arg[x]) + + -- Return argument value if provided + if not is_empty(arg_value) then + return arg_value + end + + -- Check new nested structure: extensions.iconify.x + if not is_empty(meta['extensions']) and + not is_empty(meta['extensions']['iconify']) and + not is_empty(meta['extensions']['iconify'][x]) then + return stringify(meta['extensions']['iconify'][x]) + end + + -- Check deprecated top-level structure: iconify.x (with warning) + local deprecated_value = check_deprecated_config(meta, x) + if deprecated_value then + return deprecated_value + end + + return arg_value +end + +--- Render an Iconify icon as a Pandoc RawInline for HTML output. +--- @param args table Icon arguments (icon set and name) +--- @param kwargs table Key-value options for the icon +--- @param meta table Document metadata +--- @return any Pandoc RawInline for HTML or Pandoc Null for other formats +function iconify(args, kwargs, meta) + -- detect html (excluding epub which won't handle fa) + if quarto.doc.is_format('html:js') then + ensure_html_deps() + --- @type string + local icon = stringify(args[1]) + --- @type string + local set = 'octicon' + + -- Check new nested structure for default set + if not is_empty(meta['extensions']) and + not is_empty(meta['extensions']['iconify']) and + not is_empty(meta['extensions']['iconify']['set']) then + set = stringify(meta['extensions']['iconify']['set']) + else + -- Check deprecated top-level structure for default set (with warning) + local deprecated_set = check_deprecated_config(meta, 'set') + if deprecated_set then + set = deprecated_set + end + end + + if #args > 1 and string.find(stringify(args[2]), ':') then + quarto.log.warning( + 'Use "set:icon" or "set icon" syntax, not both! ' .. + 'Using "set:icon" syntax and discarding first argument!' + ) + icon = stringify(args[2]) + end + + if string.find(icon, ':') then + set = string.sub(icon, 1, string.find(icon, ':') - 1) + icon = string.sub(icon, string.find(icon, ':') + 1) + elseif #args > 1 then + set = icon + icon = stringify(args[2]) + end + + --- @type string + local attributes = ' icon="' .. set .. ':' .. icon .. '"' + --- @type string + local default_label = 'Icon ' .. icon .. ' from ' .. set .. ' Iconify.design set.' + + --- @type string + local size = is_valid_size(get_iconify_options('size', kwargs, meta)) + --- @type string + local style = get_iconify_options('style', kwargs, meta) + + if is_empty(style) and not is_empty(size) then + attributes = attributes .. ' style="' .. size .. '"' + elseif not is_empty(style) and not is_empty(size) then + attributes = attributes .. ' style="' .. style .. ';' .. size .. '"' + elseif not is_empty(style) then + attributes = attributes .. ' style="' .. style .. '"' + end + + --- @type string + local aria_label = stringify(kwargs['label']) + if is_empty(aria_label) then + aria_label = ' aria-label="' .. default_label .. '"' + else + aria_label = ' aria-label="' .. aria_label .. '"' + end + + --- @type string + local title = stringify(kwargs['title']) + if is_empty(title) then + title = ' title="' .. default_label .. '"' + else + title = ' title="' .. title .. '"' + end + + attributes = attributes .. aria_label .. title + + --- @type string + local width = get_iconify_options('width', kwargs, meta) + if not is_empty(width) and is_empty(size) then + attributes = attributes .. ' width="' .. width .. '"' + end + --- @type string + local height = get_iconify_options('height', kwargs, meta) + if not is_empty(height) and is_empty(size) then + attributes = attributes .. ' height="' .. height .. '"' + end + --- @type string + local flip = get_iconify_options('flip', kwargs, meta) + if not is_empty(flip) then + attributes = attributes .. ' flip="' .. flip.. '"' + end + --- @type string + local rotate = get_iconify_options('rotate', kwargs, meta) + if not is_empty(rotate) then + attributes = attributes .. ' rotate="' .. rotate .. '"' + end + + --- @type string + local inline = get_iconify_options('inline', kwargs, meta) + if is_empty(inline) or inline ~= 'false' then + attributes = ' inline ' .. attributes + end + + --- @type string + local mode = get_iconify_options('mode', kwargs, meta) + --- @type table + local valid_modes = { svg = true, style = true, bg = true, mask = true } + if not is_empty(mode) and valid_modes[mode] then + attributes = attributes .. ' mode="' .. mode .. '"' + end + + return pandoc.RawInline( + 'html', + '' + ) + else + return pandoc.Null() + end +end + +--- Render Quarto icon using the iconify function with preset styling. +--- @param args table Icon arguments (ignored as we're using a preset icon) +--- @param kwargs table|nil Key-value options that might override default styling +--- @param meta table Document metadata +--- @return any Pandoc RawInline for HTML or Pandoc Null for other formats +function iconify_quarto(args, kwargs, meta) + --- @type table + local quarto_args = { 'simple-icons:quarto' } + --- @type table + local quarto_kwargs = kwargs or {} + quarto_kwargs['label'] = 'Quarto icon' + quarto_kwargs['title'] = 'Quarto icon' + --- @type string + local quarto_colour = 'color:#74aadb;' + + if not is_empty(quarto_kwargs['style']) then + --- @type string + local style = stringify(quarto_kwargs['style']) + if string.match(style, 'color:[^;]+;') then + quarto_kwargs['style'] = string.gsub(style, 'color:[^;]+;', quarto_colour) + else + quarto_kwargs['style'] = quarto_colour .. style + end + else + quarto_kwargs['style'] = quarto_colour + end + return iconify(quarto_args, quarto_kwargs, meta) +end + +--- @type table +return { + ['iconify'] = iconify, + ['quarto'] = iconify_quarto +} diff --git a/inst/Psychoco2026/banner.png b/inst/Psychoco2026/banner.png new file mode 100644 index 00000000..ce35df80 Binary files /dev/null and b/inst/Psychoco2026/banner.png differ diff --git a/inst/Psychoco2026/slides-beamer.qmd b/inst/Psychoco2026/slides-beamer.qmd new file mode 100644 index 00000000..1be2cd1f --- /dev/null +++ b/inst/Psychoco2026/slides-beamer.qmd @@ -0,0 +1,388 @@ +--- +title: "Convenient and Customizable Base R Plots" +author: "Grant McDermott, Vincent Arel-Bundock, Achim Zeileis" +format: + beamer: + slide_level: 2 + aspectratio: 169 + highlight-style: monochrome + code-block-border-left: "#999999" + theme: uibk + themeoptions: quarto,foot,nofootrule,license + header-includes: + - \URL{https://grantmcdermott.com/tinyplot/} + - \headerimage{banner.png} + - \AddToHook{env/Highlighting/begin}{\small} + - \AddToHook{env/verbatim/begin}{\small} + +pdf-engine: pdflatex + +# social media +author-mastodon: zeileis +author-mastodon-server: fosstodon.org +author-bluesky: zeileis.org +author-website: https://www.zeileis.org/ +author-email: Achim.Zeileis@R-project.org +--- + +```{r} +#| include: false +knitr::opts_chunk$set( + engine = "R", + echo = TRUE, + fig.height = 4.8, + fig.width = 6.4, + out.width = "60%" +) +set.seed(403) +``` + + +## Motivation + +::: {.callout-tip} +# Ross Ihaka & Robert Gentleman (1996) +R: A Language for Data Analysis _and Graphics_ +::: + +\medskip + +**Engines:** Base `graphics` vs. newer flexible `grid` (enabling `ggplot2` and `lattice`). + +\medskip + +**Core of base graphics:** `plot()` generic function and corresponding methods. + +\medskip + +**Default method:** Handles many basic plotting elements like points, lines, etc. + +\medskip + +**Formula method:** Handles various `y ~ x` setups. + +- Scatterplots (numeric `y` vs. numeric `x`). +- Boxplots (numeric `y` vs. categorical `x`). +- Spineplots/spinograms (categorical `y`). + + + +## Motivation + +**Illustration:** Determinants of student performance in end-term exam of an +introductory mathematics course for business and economics students at Universität Innsbruck. + +. . . + +\medskip + +```{r} +data("MathExam14W", package = "psychotools") +math <- MathExam14W |> + transform( + attempt = factor(attempt, ordered = FALSE), + points = rowSums(as.matrix(credits)^2 * (-1)^as.matrix(credits)) + ) |> + transform( + pass = factor(points >= 26, labels = c("fail", "pass")), + score = points/52 + ) |> + subset(select = c("score", "pass", "tests", "attempt", "gender")) +``` + + +## Motivation + +**Dependent variables:** + +- Numeric `score` (proportion of points). +- Binary indicator for `pass`-ing the exam (`score >= 0.5`). + +\medskip + +**Explanatory variables:** Points from previous online `tests`, `attempt`, `gender`. + +\medskip + +```{r} +summary(math) +``` + + +## Motivation: numeric ~ numeric + +```{r} +plot(score ~ tests, data = math) +``` + + +## Motivation: numeric ~ categorical + +```{r} +plot(score ~ attempt, data = math) +``` + + +## Motivation: categorical ~ numeric + +```{r} +plot(pass ~ tests, data = math) +``` + + +## Motivation: categorical ~ categorical + +```{r} +plot(pass ~ attempt, data = math) +``` + + +## Motivation: Limitations + +**So far:** + +- Nifty data visualizations. +- Intuitive, concise syntax. + +\medskip + +**Possible customizations:** + +- Groups via shading, symbols, line types, etc. +- Legends, axes, annotation. +- Grid of faceted displays. +- Layers with additional elements. + +\medskip + +**But:** + +- Requires low-level drawing of such elements. +- Tedious without intuitive, concise syntax. + + +## tinyplot + +:::: {.columns} + +::: {.column width="73%"} + +**Install:** + +```{r} +#| eval: false +install.packages("tinyplot") #or# +install.packages("tinyplot", + repos = "https://grantmcdermott.R-universe.dev") +``` + +**Load:** + +```{r} +library("tinyplot") +``` + +::: + +::: {.column width="20%"} + +![](../../altdoc/logo.png) + +::: + +:::: + +\medskip + +::: {.callout-tip} +# Starting point +`tinyplot()` or its shorthand `plt()` as drop-in replacement for `plot()`. +::: + + +## tinyplot + +```{r} +tinyplot(score ~ tests, data = math) +``` + + +## tinyplot: Features + +**Core ideas:** + +- Preservation of strengths of base R graphics. +- Lightweight extension with convenience features. +- No strong dependencies on non-base packages. +- Improved feature parity vs. `grid`-based `ggplot2` and `lattice`. +- Grouped plots with automatic legends and/or facets. +- Advanced visualization types. +- Customization via themes. + +\medskip + +**Here:** + +```{r} +tinytheme("clean2") +``` + + +## tinyplot: Themes + +```{r} +tinyplot(score ~ tests, data = math) +``` + + +## tinyplot: More plot types + +```{r} +tinyplot(score ~ tests, data = math, type = "jitter") +``` + +## tinyplot: Alpha transparency + +```{r} +tinyplot(score ~ tests, data = math, alpha = 0.4) +``` + +## tinyplot: Axis labels + +```{r} +tinyplot(score ~ tests, data = math, alpha = 0.4, yaxl = "%") +``` + +## tinyplot: Add layers + +```{r} +tinyplot(score ~ tests, data = math, alpha = 0.4, yaxl = "%") +tinyplot_add(type = "loess") +``` + +## tinyplot: Grouped plots with legends + +```{r} +tinyplot(score ~ tests | attempt, data = math, pch = "by", type = "jitter") +``` + +## tinyplot: Facets + +```{r} +#| fig-height: 5.4 +#| fig-width: 8.1 +tinyplot(score ~ tests | attempt, data = math, pch = "by", facet = "by") +``` + +## tinyplot: Facets + +```{r} +#| fig-height: 4 +#| fig-width: 12 +#| out-width: 100% +tinyplot(pass ~ tests | attempt, data = math, facet = "by", + breaks = c(9, 17, 20, 23, 26), facet.args = list(nrow = 1)) +``` + +## tinyplot: Facets + +```{r} +#| fig-height: 5.4 +#| fig-width: 11 +#| out-width: 94% +tinyplot(score ~ tests | attempt, data = math, facet = gender ~ attempt) +``` + +## tinyplot: More plot types + +```{r} +#| fig-height: 6 +#| fig-width: 8.1 +tinyplot(score ~ attempt | attempt, data = math, + type = "violin", flip = TRUE, alpha = 0.4) +``` + +## tinyplot: More plot types + +```{r} +#| fig-height: 6 +#| fig-width: 8.1 +tinyplot(attempt ~ score, data = math, bg = "white", + type = type_ridge(gradient = TRUE, col = "white")) +``` + +## tinyplot: More plot types + +**Interface:** Types can be passed as either a _string_ or _function_. + +| | | | | | +|------------|:----------------|:---------------|:---------------|:-----------| +| _String_ | `"p"` | `"ridge"` | `"loess"` | ... | +| _Function_ | `type_points()` | `type_ridge()` | `type_loess()` | ... | + + +\medskip + +**Arguments:** Can always be passed through the `type` function. + +```{r} +#| eval: false +tinyplot(..., type = type_ridge(gradient = TRUE)) +``` + +\medskip + +**Alternatively:** Through `tinyplot()` if there is no clash with top-level arguments. + +```{r} +#| eval: false +tinyplot(..., type = "ridge", gradient = TRUE) +``` + + +## Use cases + +**Teaching and interactive usage:** + +- Start with simple visualizations and fundamental principles in base graphics. +- Proceed to more complex display using the same type of interface. + +\medskip + +**Package development:** + +- Create flexible visualizations without introducing numerous dependencies. + +\medskip + +**Web R:** + +- Engine for appealing graphics without adding much overhead. + +--- + +## References + +McDermott G, Arel-Bundock V, Zeileis A (2025). + _tinyplot: Lightweight Extension of the Base R Graphics System_. + R package version 0.6.0. + [`doi:10.32614/CRAN.package.tinyplot`](https://doi.org/10.32614/CRAN.package.tinyplot) + +\medskip + +Zeileis A (2025). + "Examining Exams Using Rasch Models and Assessment of Measurement Invariance." + _Austrian Journal of Statistics_, **54**(3), 9-26. + [`doi:10.17713/ajs.v54i3.2055`](https://doi.org/10.17713/ajs.v54i3.2055) + +\medskip + +\medskip + +\medskip + +**Mastodon:** [`@{{< meta author-mastodon >}}@{{< meta author-mastodon-server >}}`](https://{{< meta author-mastodon-server >}}/@{{< meta author-mastodon >}}) + +**Bluesky:** [`@{{< meta author-bluesky >}}`](https://bsky.app/profile/{{< meta author-bluesky >}}) + +**Web:** [`{{< meta author-website >}}`]({{< meta author-website >}}) diff --git a/inst/Psychoco2026/slides-revealjs.qmd b/inst/Psychoco2026/slides-revealjs.qmd new file mode 100644 index 00000000..ae755a85 --- /dev/null +++ b/inst/Psychoco2026/slides-revealjs.qmd @@ -0,0 +1,376 @@ +--- +# title: tinyplot +title: "


" +subtitle: Convenient and Customizable Base R Plots +format: + clean-revealjs: + width: 1150 + embed-resources: true + code-line-numbers: false + highlight-style: monochrome + title-slide-attributes: + data-background-image: "../../vignettes/useR2025/img/background.png" + data-background-size: contain + include-in-header: + - text: | + +execute: + echo: true + fig-height: 4.8 + fig-width: 6.4 + out-width: 60% +author: + - name: Grant McDermott + orcid: 0000-0001-7883-8573 + - name: Vincent Arel-Bundock + orcid: 0000-0003-1995-6531 + - name: Achim Zeileis + orcid: 0000-0003-0918-3766 +institute: "Psychoco 2026" +date: 2026-02-06 +extensions: iconify +--- + +## Motivation + +::: {.callout-tip} +# Ross Ihaka & Robert Gentleman (1996) +R: A Language for Data Analysis _and Graphics_ +::: + + +**Engines:** Base `graphics` vs. newer flexible `grid` (enabling `ggplot2` and `lattice`). + +**Core of base graphics:** `plot()` generic function and corresponding methods. + +**Default method:** Handles many basic plotting elements like points, lines, etc. + +**Formula method:** Handles various `y ~ x` setups. + +- Scatterplots (numeric `y` vs. numeric `x`). +- Boxplots (numeric `y` vs. categorical `x`). +- Spineplots/spinograms (categorical `y`). + + + +## Motivation + +**Illustration:** Determinants of student performance in end-term exam of an +introductory mathematics course for business and economics students at Universität Innsbruck. + +```{r Prepr} +#| code-fold: true +#| code-summary: "Data preparation" +data("MathExam14W", package = "psychotools") +math <- MathExam14W |> + transform( + attempt = factor(attempt, ordered = FALSE), + points = rowSums(as.matrix(credits)^2 * (-1)^as.matrix(credits)) + ) |> + transform( + pass = factor(points >= 26, labels = c("fail", "pass")), + score = points/52 + ) |> + subset(select = c("score", "pass", "tests", "attempt", "gender")) +``` + + +## Motivation + +**Dependent variables:** + +- Numeric `score` (proportion of points). +- Binary indicator for `pass`-ing the exam (`score >= 0.5`). + +**Explanatory variables:** Points from previous online `tests`, `attempt`, `gender`. + +```{r} +summary(math) +``` + + +## Motivation: numeric ~ numeric + +```{r} +plot(score ~ tests, data = math) +``` + + +## Motivation: numeric ~ categorical + +```{r} +plot(score ~ attempt, data = math) +``` + + +## Motivation: categorical ~ numeric + +```{r} +plot(pass ~ tests, data = math) +``` + + +## Motivation: categorical ~ categorical + +```{r} +plot(pass ~ attempt, data = math) +``` + + +## Motivation: Limitations + +**So far:** + +- Nifty data visualizations. +- Intuitive, concise syntax. + + + +**Possible customizations:** + +- Groups via shading, symbols, line types, etc. +- Legends, axes, annotation. +- Grid of faceted displays. +- Layers with additional elements. + + + +**But:** + +- Requires low-level drawing of such elements. +- Tedious without intuitive, concise syntax. + + +## tinyplot + +:::: {.columns} + +::: {.column width="73%"} + +**Install:** + +```{r} +#| eval: false +install.packages("tinyplot") #or# +install.packages("tinyplot", + repos = "https://grantmcdermott.R-universe.dev") +``` + +**Load:** + +```{r} +library("tinyplot") +``` + +::: + +::: {.column width="7%"} + +::: + +::: {.column width="20%"} + +![](../../altdoc/logo.png) + +::: + +:::: + + +  + + +::: {.callout-tip} +# Starting point +`tinyplot()` or its shorthand `plt()` as drop-in replacement for `plot()`. +::: + + +## tinyplot + +```{r} +tinyplot(score ~ tests, data = math) +``` + + +## tinyplot: Features + +**Core ideas:** + +- Preservation of strengths of base R graphics. +- Lightweight extension with convenience features. +- No strong dependencies on non-base packages. +- Improved feature parity vs. `grid`-based `ggplot2` and `lattice`. +- Grouped plots with automatic legends and/or facets. +- Advanced visualization types. +- Customization via themes. + +**Here:** + +```{r} +tinytheme("clean2") +``` + + +## tinyplot: Themes + +```{r} +tinyplot(score ~ tests, data = math) +``` + + +## tinyplot: More plot types + +```{r} +tinyplot(score ~ tests, data = math, type = "jitter") +``` + +## tinyplot: Alpha transparency + +```{r} +tinyplot(score ~ tests, data = math, alpha = 0.4) +``` + +## tinyplot: Axis labels + +```{r} +tinyplot(score ~ tests, data = math, alpha = 0.4, yaxl = "%") +``` + +## tinyplot: Add layers + +```{r} +tinyplot(score ~ tests, data = math, alpha = 0.4, yaxl = "%") +tinyplot_add(type = "loess") +``` + +## tinyplot: Grouped plots with legends + +```{r} +tinyplot(score ~ tests | attempt, data = math, pch = "by", type = "jitter") +``` + +## tinyplot: Facets + +```{r} +#| fig-height: 5.4 +#| fig-width: 8.1 +tinyplot(score ~ tests | attempt, data = math, pch = "by", facet = "by") +``` + +## tinyplot: Facets + +```{r} +#| fig-height: 4 +#| fig-width: 12 +#| out-width: 100% +tinyplot(pass ~ tests | attempt, data = math, facet = "by", + breaks = c(9, 17, 20, 23, 26), facet.args = list(nrow = 1)) +``` + +## tinyplot: Facets + +```{r} +#| fig-height: 5.4 +#| fig-width: 11 +#| out-width: 94% +tinyplot(score ~ tests | attempt, data = math, facet = gender ~ attempt) +``` + +## tinyplot: More plot types + +```{r} +#| fig-height: 6 +#| fig-width: 8.1 +tinyplot(score ~ attempt | attempt, data = math, + type = "violin", flip = TRUE, alpha = 0.4) +``` + +## tinyplot: More plot types + +```{r} +#| fig-height: 6 +#| fig-width: 8.1 +tinyplot(attempt ~ score, data = math, bg = "white", + type = type_ridge(gradient = TRUE, col = "white")) +``` + +## tinyplot: More plot types + +**Interface:** Types can be passed as either a _string_ or _function_. + +| | | | | | +|------------|:----------------|:---------------|:---------------|:-----------| +| _String_ | `"p"` | `"ridge"` | `"loess"` | ... | +| _Function_ | `type_points()` | `type_ridge()` | `type_loess()` | ... | + + + + +**Arguments:** Can always be passed through the `type` function. + +```{r} +#| eval: false +tinyplot(..., type = type_ridge(gradient = TRUE)) +``` + + + +**Alternatively:** Through `tinyplot()` if there is no clash with top-level arguments. + +```{r} +#| eval: false +tinyplot(..., type = "ridge", gradient = TRUE) +``` + + +## Use cases + +**Teaching and interactive usage:** + +- Start with simple visualizations and fundamental principles in base graphics. +- Proceed to more complex display using the same type of interface. + +**Package development:** + +- Create flexible visualizations without introducing numerous dependencies. + +**Web R:** + +- Engine for appealing graphics without adding much overhead. + +--- + +## References + +McDermott G, Arel-Bundock V, Zeileis A (2025). + _tinyplot: Lightweight Extension of the Base R Graphics System_. + R package version 0.6.0. + [`doi:10.32614/CRAN.package.tinyplot`](https://doi.org/10.32614/CRAN.package.tinyplot) + +Zeileis A (2025). + "Examining Exams Using Rasch Models and Assessment of Measurement Invariance." + _Austrian Journal of Statistics_, **54**(3), 9-26. + [`doi:10.17713/ajs.v54i3.2055`](https://doi.org/10.17713/ajs.v54i3.2055) + + +{{< iconify mdi mastodon >}}   [`@zeileis@fosstodon.org`](https://fosstodon.org/@zeileis) + +{{< iconify fa6-brands bluesky >}}   [`@zeileis.org`](https://bsky.app/profile/zeileis.org) + +{{< iconify mdi link-variant >}}   [`https://www.zeileis.org/`](https://www.zeileis.org/)