├── .DS_Store ├── README.md ├── dashboard ├── dashboard.html ├── dashboard.qmd ├── dashboard_files │ └── libs │ │ ├── bootstrap │ │ ├── bootstrap-icons.css │ │ ├── bootstrap-icons.woff │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.js │ │ ├── clipboard │ │ └── clipboard.min.js │ │ ├── quarto-dashboard │ │ ├── components.js │ │ ├── quarto-dashboard.js │ │ └── web-components.js │ │ ├── quarto-html │ │ ├── anchor.min.js │ │ ├── popper.min.js │ │ ├── quarto-syntax-highlighting.css │ │ ├── quarto.js │ │ ├── tippy.css │ │ └── tippy.umd.min.js │ │ └── quarto-ojs │ │ ├── quarto-ojs-runtime.js │ │ └── quarto-ojs.css ├── data │ └── tswift.csv ├── theme.scss └── tswift.jpeg └── presentation ├── .DS_Store ├── data └── big_tech_stock_prices.csv ├── images ├── headshot.jpeg ├── intro-observable.qmd ├── intro.webp ├── observable.png ├── observable.webp ├── philly.jpeg └── title-slide.png ├── intro-observable.html ├── intro-observable.qmd └── theme.scss /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tashapiro/intro-observable/2c44e8f252d49de3a5271a096c458331ae908f01/.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Intro to Observable Plot 2 | 3 | ## Overview 4 | 5 | This is an introductory talk covering a range of possibilities with Observable Plot From constructing simple plots, to layering in interactive components, to seeing it in action with Quarto - participants will get to sample a variety of Observable Plot applications. 6 | 7 | The first section participants are invited to walk through an Observable Notebook analyzing Taylor Swift songs. 8 | 9 | In the second section, participants will learn how they can integrate Observable Plot with Quarto (qmd) files using RStudio. 10 | 11 | ![](presentation/images/intro.webp) 12 | 13 | ## Additional Resources 14 | 15 | - [Observable Notebook](https://observablehq.com/d/f002f055d6fa2f8d) 16 | 17 | - [Slides](https://tashapiro.github.io/intro-observable/presentation/intro-observable.html#/TitleSlide) 18 | 19 | - [Quarto Dashboard Example](https://tashapiro.github.io/intro-observable/dashboard/dashboard.html) 20 | 21 | **Note**: The Quarto Dashboard uses the pre-release version of Quarto, 1.4. To run this example, users will need to download this version. 22 | -------------------------------------------------------------------------------- /dashboard/dashboard.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Taylor Swift Song Analysis" 3 | format: 4 | dashboard: 5 | logo: tswift.jpeg 6 | theme: "theme.scss" 7 | --- 8 | 9 | ```{ojs} 10 | //| output: false 11 | 12 | //upload data 13 | songs = FileAttachment("data/tswift.csv").csv({ typed: true }) 14 | 15 | //get list of alums 16 | albums = [...new Set(songs.map(d => d.album_name))].sort() 17 | 18 | filtered_albums = [...new Set(filtered_songs.map(d => d.album_name))].sort() 19 | 20 | 21 | //list of audio features 22 | features = ["acousticness","danceability","energy", "speechiness", "instrumentalness", "liveness", "valence"] 23 | 24 | // function from Fil's notebook to calculate correlation matrix 25 | function corr(x, y) { 26 | const n = x.length; 27 | if (y.length !== n) 28 | throw new Error("The two columns must have the same length."); 29 | const x_ = d3.mean(x); 30 | const y_ = d3.mean(y); 31 | const XY = d3.sum(x, (_, i) => (x[i] - x_) * (y[i] - y_)); 32 | const XX = d3.sum(x, (d) => (d - x_) ** 2); 33 | const YY = d3.sum(y, (d) => (d - y_) ** 2); 34 | return XY / Math.sqrt(XX * YY); 35 | } 36 | 37 | //set up correlation matrix 38 | correlations = d3.cross(features, features).map(([a, b]) => ({ 39 | a, 40 | b, 41 | correlation: corr(Plot.valueof(filtered_songs, a), Plot.valueof(filtered_songs, b)) 42 | })) 43 | 44 | 45 | filtered_songs_album = songs.filter(d => selectAlbums.includes(d.album_name)) 46 | 47 | ``` 48 | 49 | # Sidebar {.sidebar} 50 | 51 | ```{ojs} 52 | //select album input, multi-select 53 | viewof selectAlbums = Inputs.select(albums, {label:"Albums", value:albums, multiple:10}) 54 | 55 | ``` 56 | 57 | **Minimums** 58 | 59 | ```{ojs} 60 | 61 | //Input form of audio feature sliders 62 | viewof sliders = ( 63 | Inputs.form({ 64 | energy : Inputs.range([0,1], {label:"Energy", step:0.01, value:0}), 65 | valence : Inputs.range([0,1], {label:"Valence", step:0.01, value:0}), 66 | acousticness : Inputs.range([0,1], {label:"Acousticness", step:0.01, value:0}), 67 | danceability : Inputs.range([0,1], {label:"Danceability", step:0.01, value:0}) 68 | }) 69 | ) 70 | ``` 71 | 72 | # Summary 73 | 74 | ## Value Boxes {height="150px"} 75 | 76 | ### Albums 77 | 78 | ```{ojs} 79 | //| component: valuebox 80 | //| title: "Albums" 81 | //| icon: vinyl-fill 82 | //| color: light 83 | filtered_albums.length 84 | ``` 85 | 86 | ### Songs 87 | 88 | ```{ojs} 89 | //| component: valuebox 90 | //| title: "Songs" 91 | //| icon: music-note-beamed 92 | //| color: light 93 | filtered_songs.length 94 | ``` 95 | 96 | ### Avg Energy 97 | 98 | ```{ojs} 99 | //| component: valuebox 100 | //| title: "Avg Energy" 101 | //| icon: lightning-fill 102 | //| color: light 103 | parseFloat(d3.mean(filtered_songs, d => d.energy).toFixed(2)) 104 | ``` 105 | 106 | ## Plots 107 | 108 | #### Plot 1 109 | 110 | ::: {.card title="Feature Correlation Matrix"} 111 | ```{ojs} 112 | 113 | 114 | 115 | Plot.plot({ 116 | marginBottom:70, 117 | marginLeft:100, 118 | //adjust plot dimensions 119 | //adjust plot labels 120 | label: null, //specific for x and y axis labels 121 | style: {fontFamily: "Roboto"}, 122 | //customize x and y axis 123 | y: {tickSize:0}, 124 | x: {tickSize:0}, 125 | //customize legend 126 | color: { scheme: "PuOr", pivot: 0, legend: true, label: "Correlation" }, 127 | marks: [ 128 | Plot.cell(correlations, { x: "a", y: "b", fill: "correlation" }), 129 | Plot.text(correlations, { 130 | x: "a", 131 | y: "b", 132 | fontSize: 15, 133 | //map using functions to do something additional with data 134 | text: d => d.correlation.toFixed(2), // round corr values to 2nd decimal 135 | fill: d => (Math.abs(d.correlation) > 0.6 ? "white" : "black") //if then function for text color 136 | }) 137 | ] 138 | }) 139 | ``` 140 | ::: 141 | 142 | #### Plot 2 143 | 144 | ::: {.card title="Feature Cross Analysis"} 145 | 146 | ```{ojs} 147 | viewof filters = ( 148 | Inputs.form({ 149 | selectX : Inputs.select(features, {label:"Select X", value:"energy"}), 150 | selectY : Inputs.select(features, {label:"Select Y", value:"acousticness"}), 151 | }, 152 | {template}) 153 | ) 154 | ``` 155 | 156 | ```{ojs} 157 | 158 | Plot.plot({ 159 | grid: true, 160 | marginBottom:20, 161 | marks: [ 162 | Plot.ruleY([0]), 163 | Plot.dot(filtered_songs, {x: filters.selectX, 164 | y: filters.selectY, 165 | title: "track_name", 166 | fill: "#401487", 167 | stroke: "white", 168 | opacity: 0.9, 169 | r: 6}) 170 | ] 171 | }) 172 | 173 | ``` 174 | ::: 175 | 176 | # Songs 177 | 178 | ## Table 179 | 180 | ```{ojs} 181 | // | output: false 182 | 183 | // set up DuckDB for querying 184 | db = DuckDBClient.of({ 185 | songs: filtered_songs_album, 186 | }) 187 | 188 | ``` 189 | 190 | ```{ojs} 191 | // | output: false 192 | 193 | //use SQL to apply filters and return a new array 194 | filtered_songs = db.sql`SELECT 195 | track_name 196 | ,album_name 197 | ,round(liveness,2) as liveness 198 | ,round(valence,2) as valence 199 | ,round(energy,2) as energy 200 | ,round(acousticness,2) as acousticness 201 | ,danceability 202 | ,speechiness 203 | ,instrumentalness 204 | FROM songs 205 | WHERE 206 | valence >= ${sliders.valence} 207 | and album_name <> 'NA' 208 | and energy >= ${sliders.energy} 209 | and danceability>= ${sliders.danceability} 210 | and acousticness >= ${sliders.acousticness}` 211 | ``` 212 | 213 | ```{ojs} 214 | // | output: false 215 | function heatmap(value, minValue, maxValue) { 216 | // Calculate the color scale based on the value's position 217 | var scale = (value - minValue) / (maxValue - minValue); 218 | var color = getColorFromScale(scale); 219 | 220 | var fontColor = value > 0.3 ? "white" : "black"; 221 | 222 | // Create the output div with the scaled background color 223 | return htl.html` 224 |
225 | ${value} 226 |
227 | `; 228 | } 229 | 230 | 231 | function getColorFromScale(scale) { 232 | 233 | var colors = d3.schemeBuPu[7] 234 | 235 | // Calculate the index of the color in the color ramp based on the scale 236 | var index = Math.floor(scale * (colors.length - 1)); 237 | 238 | // Get the start and end colors from the color ramp 239 | var startColor = hexToRgb(colors[index]); 240 | var endColor = hexToRgb(colors[index + 1]); 241 | 242 | // Helper function to convert hex color to RGB 243 | function hexToRgb(hex) { 244 | var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 245 | hex = hex.replace(shorthandRegex, function (m, r, g, b) { 246 | return r + r + g + g + b + b; 247 | }); 248 | 249 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 250 | return result ? [ 251 | parseInt(result[1], 16), 252 | parseInt(result[2], 16), 253 | parseInt(result[3], 16) 254 | ] : [255, 255, 255]; // Default to white if invalid hex color 255 | } 256 | 257 | // Calculate the intermediate color based on the scale 258 | var color = startColor.map(function (channel, i) { 259 | var range = endColor[i] - channel; 260 | return Math.round(channel + range * (scale * (colors.length - 1) - index)); 261 | }); 262 | 263 | // Return the color in RGB format 264 | return "rgb(" + color.join(",") + ")"; 265 | } 266 | 267 | ``` 268 | 269 | ::: {.card title="Songs"} 270 | ```{ojs} 271 | Inputs.table(filtered_songs, { 272 | //how many rows to display 273 | rows:40, 274 | //format table column names 275 | header: { 276 | album_name: "Album Name", 277 | track_name: "Track", 278 | liveness: "Liveness", 279 | energy: "Energy", 280 | acousticness: "Acousticness", 281 | valence: "Valence", 282 | speechiness: "Speechiness" 283 | }, 284 | //adjust column width 285 | width: { 286 | track_name: "25%", 287 | album_name: "15%", 288 | liveness: "12%", 289 | valence: "12%", 290 | acousticness: "12%", 291 | energy: "12%", 292 | speechiness: "12%" 293 | }, 294 | //align columns 295 | align: { 296 | liveness: "center", 297 | valence: "center", 298 | energy: "center", 299 | acousticness: "center", 300 | speechiness: "center" 301 | }, 302 | //format with additional functions 303 | format: { 304 | liveness: x => heatmap(x,0,1), 305 | energy: x => heatmap(x,0,1), 306 | valence: x => heatmap(x,0,1), 307 | acousticness: x => heatmap(x,0,1), 308 | speechiness: x => heatmap(x,0,1) 309 | } 310 | }) 311 | ``` 312 | ::: 313 | 314 | ```{ojs} 315 | //| output: false 316 | //| 317 | // Template credit: This layout is updated from Martien van Steenbergen @martien/horizontal-inputs 318 | 319 | template = (inputs) => 320 | htl.html`
${Object.values(inputs)}
321 | ` 334 | 335 | ``` -------------------------------------------------------------------------------- /dashboard/dashboard_files/libs/bootstrap/bootstrap-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tashapiro/intro-observable/2c44e8f252d49de3a5271a096c458331ae908f01/dashboard/dashboard_files/libs/bootstrap/bootstrap-icons.woff -------------------------------------------------------------------------------- /dashboard/dashboard_files/libs/clipboard/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.11 3 | * https://clipboardjs.com/ 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !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 n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1 { 4 | // srcts/src/components/_utils.ts 5 | var InputBinding = window.Shiny ? Shiny.InputBinding : class { 6 | }; 7 | function registerBinding(inputBindingClass, name) { 8 | if (window.Shiny) { 9 | Shiny.inputBindings.register(new inputBindingClass(), "bslib." + name); 10 | } 11 | } 12 | function hasDefinedProperty(obj, prop) { 13 | return Object.prototype.hasOwnProperty.call(obj, prop) && obj[prop] !== void 0; 14 | } 15 | function getAllFocusableChildren(el) { 16 | const base = [ 17 | "a[href]", 18 | "area[href]", 19 | "button", 20 | "details summary", 21 | "input", 22 | "iframe", 23 | "select", 24 | "textarea", 25 | '[contentEditable=""]', 26 | '[contentEditable="true"]', 27 | '[contentEditable="TRUE"]', 28 | "[tabindex]" 29 | ]; 30 | const modifiers = [':not([tabindex="-1"])', ":not([disabled])"]; 31 | const selectors = base.map((b) => b + modifiers.join("")); 32 | const focusable = el.querySelectorAll(selectors.join(", ")); 33 | return Array.from(focusable); 34 | } 35 | 36 | // srcts/src/components/accordion.ts 37 | var AccordionInputBinding = class extends InputBinding { 38 | find(scope) { 39 | return $(scope).find(".accordion.bslib-accordion-input"); 40 | } 41 | getValue(el) { 42 | const items = this._getItemInfo(el); 43 | const selected = items.filter((x) => x.isOpen()).map((x) => x.value); 44 | return selected.length === 0 ? null : selected; 45 | } 46 | subscribe(el, callback) { 47 | $(el).on( 48 | "shown.bs.collapse.accordionInputBinding hidden.bs.collapse.accordionInputBinding", 49 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 50 | function(event) { 51 | callback(true); 52 | } 53 | ); 54 | } 55 | unsubscribe(el) { 56 | $(el).off(".accordionInputBinding"); 57 | } 58 | receiveMessage(el, data) { 59 | const method = data.method; 60 | if (method === "set") { 61 | this._setItems(el, data); 62 | } else if (method === "open") { 63 | this._openItems(el, data); 64 | } else if (method === "close") { 65 | this._closeItems(el, data); 66 | } else if (method === "remove") { 67 | this._removeItem(el, data); 68 | } else if (method === "insert") { 69 | this._insertItem(el, data); 70 | } else if (method === "update") { 71 | this._updateItem(el, data); 72 | } else { 73 | throw new Error(`Method not yet implemented: ${method}`); 74 | } 75 | } 76 | _setItems(el, data) { 77 | const items = this._getItemInfo(el); 78 | const vals = this._getValues(el, items, data.values); 79 | items.forEach((x) => { 80 | vals.indexOf(x.value) > -1 ? x.show() : x.hide(); 81 | }); 82 | } 83 | _openItems(el, data) { 84 | const items = this._getItemInfo(el); 85 | const vals = this._getValues(el, items, data.values); 86 | items.forEach((x) => { 87 | if (vals.indexOf(x.value) > -1) 88 | x.show(); 89 | }); 90 | } 91 | _closeItems(el, data) { 92 | const items = this._getItemInfo(el); 93 | const vals = this._getValues(el, items, data.values); 94 | items.forEach((x) => { 95 | if (vals.indexOf(x.value) > -1) 96 | x.hide(); 97 | }); 98 | } 99 | _insertItem(el, data) { 100 | let targetItem = this._findItem(el, data.target); 101 | if (!targetItem) { 102 | targetItem = data.position === "before" ? el.firstElementChild : el.lastElementChild; 103 | } 104 | const panel = data.panel; 105 | if (targetItem) { 106 | Shiny.renderContent( 107 | targetItem, 108 | panel, 109 | data.position === "before" ? "beforeBegin" : "afterEnd" 110 | ); 111 | } else { 112 | Shiny.renderContent(el, panel); 113 | } 114 | if (this._isAutoClosing(el)) { 115 | const val = $(panel.html).attr("data-value"); 116 | $(el).find(`[data-value="${val}"] .accordion-collapse`).attr("data-bs-parent", "#" + el.id); 117 | } 118 | } 119 | _removeItem(el, data) { 120 | const targetItems = this._getItemInfo(el).filter( 121 | (x) => data.target.indexOf(x.value) > -1 122 | ); 123 | const unbindAll = Shiny == null ? void 0 : Shiny.unbindAll; 124 | targetItems.forEach((x) => { 125 | if (unbindAll) 126 | unbindAll(x.item); 127 | x.item.remove(); 128 | }); 129 | } 130 | _updateItem(el, data) { 131 | const target = this._findItem(el, data.target); 132 | if (!target) { 133 | throw new Error( 134 | `Unable to find an accordion_panel() with a value of ${data.target}` 135 | ); 136 | } 137 | if (hasDefinedProperty(data, "value")) { 138 | target.dataset.value = data.value; 139 | } 140 | if (hasDefinedProperty(data, "body")) { 141 | const body = target.querySelector(".accordion-body"); 142 | Shiny.renderContent(body, data.body); 143 | } 144 | const header = target.querySelector(".accordion-header"); 145 | if (hasDefinedProperty(data, "title")) { 146 | const title = header.querySelector(".accordion-title"); 147 | Shiny.renderContent(title, data.title); 148 | } 149 | if (hasDefinedProperty(data, "icon")) { 150 | const icon = header.querySelector( 151 | ".accordion-button > .accordion-icon" 152 | ); 153 | Shiny.renderContent(icon, data.icon); 154 | } 155 | } 156 | _getItemInfo(el) { 157 | const items = Array.from( 158 | el.querySelectorAll(":scope > .accordion-item") 159 | ); 160 | return items.map((x) => this._getSingleItemInfo(x)); 161 | } 162 | _getSingleItemInfo(x) { 163 | const collapse = x.querySelector(".accordion-collapse"); 164 | const isOpen = () => $(collapse).hasClass("show"); 165 | return { 166 | item: x, 167 | value: x.dataset.value, 168 | isOpen, 169 | show: () => { 170 | if (!isOpen()) 171 | $(collapse).collapse("show"); 172 | }, 173 | hide: () => { 174 | if (isOpen()) 175 | $(collapse).collapse("hide"); 176 | } 177 | }; 178 | } 179 | _getValues(el, items, values) { 180 | let vals = values !== true ? values : items.map((x) => x.value); 181 | const autoclose = this._isAutoClosing(el); 182 | if (autoclose) { 183 | vals = vals.slice(vals.length - 1, vals.length); 184 | } 185 | return vals; 186 | } 187 | _findItem(el, value) { 188 | return el.querySelector(`[data-value="${value}"]`); 189 | } 190 | _isAutoClosing(el) { 191 | return el.classList.contains("autoclose"); 192 | } 193 | }; 194 | registerBinding(AccordionInputBinding, "accordion"); 195 | 196 | // srcts/src/components/_shinyResizeObserver.ts 197 | var ShinyResizeObserver = class { 198 | /** 199 | * Watch containers for size changes and ensure that Shiny outputs and 200 | * htmlwidgets within resize appropriately. 201 | * 202 | * @details 203 | * The ShinyResizeObserver is used to watch the containers, such as Sidebars 204 | * and Cards for size changes, in particular when the sidebar state is toggled 205 | * or the card body is expanded full screen. It performs two primary tasks: 206 | * 207 | * 1. Dispatches a `resize` event on the window object. This is necessary to 208 | * ensure that Shiny outputs resize appropriately. In general, the window 209 | * resizing is throttled and the output update occurs when the transition 210 | * is complete. 211 | * 2. If an output with a resize method on the output binding is detected, we 212 | * directly call the `.onResize()` method of the binding. This ensures that 213 | * htmlwidgets transition smoothly. In static mode, htmlwidgets does this 214 | * already. 215 | * 216 | * @note 217 | * This resize observer also handles race conditions in some complex 218 | * fill-based layouts with multiple outputs (e.g., plotly), where shiny 219 | * initializes with the correct sizing, but in-between the 1st and last 220 | * renderValue(), the size of the output containers can change, meaning every 221 | * output but the 1st gets initialized with the wrong size during their 222 | * renderValue(). Then, after the render phase, shiny won't know to trigger a 223 | * resize since all the widgets will return to their original size (and thus, 224 | * Shiny thinks there isn't any resizing to do). The resize observer works 225 | * around this by ensuring that the output is resized whenever its container 226 | * size changes. 227 | * @constructor 228 | */ 229 | constructor() { 230 | this.resizeObserverEntries = []; 231 | this.resizeObserver = new ResizeObserver((entries) => { 232 | const resizeEvent = new Event("resize"); 233 | window.dispatchEvent(resizeEvent); 234 | if (!window.Shiny) 235 | return; 236 | const resized = []; 237 | for (const entry of entries) { 238 | if (!(entry.target instanceof HTMLElement)) 239 | continue; 240 | if (!entry.target.querySelector(".shiny-bound-output")) 241 | continue; 242 | entry.target.querySelectorAll(".shiny-bound-output").forEach((el) => { 243 | if (resized.includes(el)) 244 | return; 245 | const { binding, onResize } = $(el).data("shinyOutputBinding"); 246 | if (!binding || !binding.resize) 247 | return; 248 | const owner = el.shinyResizeObserver; 249 | if (owner && owner !== this) 250 | return; 251 | if (!owner) 252 | el.shinyResizeObserver = this; 253 | onResize(el); 254 | resized.push(el); 255 | if (!el.classList.contains("shiny-plot-output")) 256 | return; 257 | const img = el.querySelector( 258 | 'img:not([width="100%"])' 259 | ); 260 | if (img) 261 | img.setAttribute("width", "100%"); 262 | }); 263 | } 264 | }); 265 | } 266 | /** 267 | * Observe an element for size changes. 268 | * @param {HTMLElement} el - The element to observe. 269 | */ 270 | observe(el) { 271 | this.resizeObserver.observe(el); 272 | this.resizeObserverEntries.push(el); 273 | } 274 | /** 275 | * Stop observing an element for size changes. 276 | * @param {HTMLElement} el - The element to stop observing. 277 | */ 278 | unobserve(el) { 279 | const idxEl = this.resizeObserverEntries.indexOf(el); 280 | if (idxEl < 0) 281 | return; 282 | this.resizeObserver.unobserve(el); 283 | this.resizeObserverEntries.splice(idxEl, 1); 284 | } 285 | /** 286 | * This method checks that we're not continuing to watch elements that no 287 | * longer exist in the DOM. If any are found, we stop observing them and 288 | * remove them from our array of observed elements. 289 | * 290 | * @private 291 | * @static 292 | */ 293 | flush() { 294 | this.resizeObserverEntries.forEach((el) => { 295 | if (!document.body.contains(el)) 296 | this.unobserve(el); 297 | }); 298 | } 299 | }; 300 | 301 | // srcts/src/components/card.ts 302 | var _Card = class { 303 | /** 304 | * Creates an instance of a bslib Card component. 305 | * 306 | * @constructor 307 | * @param {HTMLElement} card 308 | */ 309 | constructor(card) { 310 | var _a; 311 | card.removeAttribute(_Card.attr.ATTR_INIT); 312 | (_a = card.querySelector(`script[${_Card.attr.ATTR_INIT}]`)) == null ? void 0 : _a.remove(); 313 | this.card = card; 314 | _Card.instanceMap.set(card, this); 315 | _Card.shinyResizeObserver.observe(this.card); 316 | this._addEventListeners(); 317 | this.overlay = this._createOverlay(); 318 | this._exitFullScreenOnEscape = this._exitFullScreenOnEscape.bind(this); 319 | this._trapFocusExit = this._trapFocusExit.bind(this); 320 | } 321 | /** 322 | * Enter the card's full screen mode, either programmatically or via an event 323 | * handler. Full screen mode is activated by adding a class to the card that 324 | * positions it absolutely and expands it to fill the viewport. In addition, 325 | * we add a full screen overlay element behind the card and we trap focus in 326 | * the expanded card while in full screen mode. 327 | * 328 | * @param {?Event} [event] 329 | */ 330 | enterFullScreen(event) { 331 | var _a; 332 | if (event) 333 | event.preventDefault(); 334 | document.addEventListener("keydown", this._exitFullScreenOnEscape, false); 335 | document.addEventListener("keydown", this._trapFocusExit, true); 336 | this.card.setAttribute(_Card.attr.ATTR_FULL_SCREEN, "true"); 337 | document.body.classList.add(_Card.attr.CLASS_HAS_FULL_SCREEN); 338 | this.card.insertAdjacentElement("beforebegin", this.overlay.container); 339 | if (!this.card.contains(document.activeElement) || ((_a = document.activeElement) == null ? void 0 : _a.classList.contains( 340 | _Card.attr.CLASS_FULL_SCREEN_ENTER 341 | ))) { 342 | this.card.setAttribute("tabindex", "-1"); 343 | this.card.focus(); 344 | } 345 | } 346 | /** 347 | * Exit full screen mode. This removes the full screen overlay element, 348 | * removes the full screen class from the card, and removes the keyboard event 349 | * listeners that were added when entering full screen mode. 350 | */ 351 | exitFullScreen() { 352 | document.removeEventListener( 353 | "keydown", 354 | this._exitFullScreenOnEscape, 355 | false 356 | ); 357 | document.removeEventListener("keydown", this._trapFocusExit, true); 358 | this.overlay.container.remove(); 359 | this.card.setAttribute(_Card.attr.ATTR_FULL_SCREEN, "false"); 360 | this.card.removeAttribute("tabindex"); 361 | document.body.classList.remove(_Card.attr.CLASS_HAS_FULL_SCREEN); 362 | } 363 | /** 364 | * Adds general card-specific event listeners. 365 | * @private 366 | */ 367 | _addEventListeners() { 368 | const btnFullScreen = this.card.querySelector( 369 | `:scope > * > .${_Card.attr.CLASS_FULL_SCREEN_ENTER}` 370 | ); 371 | if (!btnFullScreen) 372 | return; 373 | btnFullScreen.addEventListener("click", (ev) => this.enterFullScreen(ev)); 374 | } 375 | /** 376 | * An event handler to exit full screen mode when the Escape key is pressed. 377 | * @private 378 | * @param {KeyboardEvent} event 379 | */ 380 | _exitFullScreenOnEscape(event) { 381 | if (!(event.target instanceof HTMLElement)) 382 | return; 383 | const selOpenSelectInput = ["select[open]", "input[aria-expanded='true']"]; 384 | if (event.target.matches(selOpenSelectInput.join(", "))) 385 | return; 386 | if (event.key === "Escape") { 387 | this.exitFullScreen(); 388 | } 389 | } 390 | /** 391 | * An event handler to trap focus within the card when in full screen mode. 392 | * 393 | * @description 394 | * This keyboard event handler ensures that tab focus stays within the card 395 | * when in full screen mode. When the card is first expanded, 396 | * we move focus to the card element itself. If focus somehow leaves the card, 397 | * we returns focus to the card container. 398 | * 399 | * Within the card, we handle only tabbing from the close anchor or the last 400 | * focusable element and only when tab focus would have otherwise left the 401 | * card. In those cases, we cycle focus to the last focusable element or back 402 | * to the anchor. If the card doesn't have any focusable elements, we move 403 | * focus to the close anchor. 404 | * 405 | * @note 406 | * Because the card contents may change, we check for focusable elements 407 | * every time the handler is called. 408 | * 409 | * @private 410 | * @param {KeyboardEvent} event 411 | */ 412 | _trapFocusExit(event) { 413 | if (!(event instanceof KeyboardEvent)) 414 | return; 415 | if (event.key !== "Tab") 416 | return; 417 | const isFocusedContainer = event.target === this.card; 418 | const isFocusedAnchor = event.target === this.overlay.anchor; 419 | const isFocusedWithin = this.card.contains(event.target); 420 | const stopEvent = () => { 421 | event.preventDefault(); 422 | event.stopImmediatePropagation(); 423 | }; 424 | if (!(isFocusedWithin || isFocusedContainer || isFocusedAnchor)) { 425 | stopEvent(); 426 | this.card.focus(); 427 | return; 428 | } 429 | const focusableElements = getAllFocusableChildren(this.card).filter( 430 | (el) => !el.classList.contains(_Card.attr.CLASS_FULL_SCREEN_ENTER) 431 | ); 432 | const hasFocusableElements = focusableElements.length > 0; 433 | if (!hasFocusableElements) { 434 | stopEvent(); 435 | this.overlay.anchor.focus(); 436 | return; 437 | } 438 | if (isFocusedContainer) 439 | return; 440 | const lastFocusable = focusableElements[focusableElements.length - 1]; 441 | const isFocusedLast = event.target === lastFocusable; 442 | if (isFocusedAnchor && event.shiftKey) { 443 | stopEvent(); 444 | lastFocusable.focus(); 445 | return; 446 | } 447 | if (isFocusedLast && !event.shiftKey) { 448 | stopEvent(); 449 | this.overlay.anchor.focus(); 450 | return; 451 | } 452 | } 453 | /** 454 | * Creates the full screen overlay. 455 | * @private 456 | * @returns {CardFullScreenOverlay} 457 | */ 458 | _createOverlay() { 459 | const container = document.createElement("div"); 460 | container.id = _Card.attr.ID_FULL_SCREEN_OVERLAY; 461 | container.onclick = this.exitFullScreen.bind(this); 462 | const anchor = this._createOverlayCloseAnchor(); 463 | container.appendChild(anchor); 464 | return { container, anchor }; 465 | } 466 | /** 467 | * Creates the anchor element used to exit the full screen mode. 468 | * @private 469 | * @returns {HTMLAnchorElement} 470 | */ 471 | _createOverlayCloseAnchor() { 472 | const anchor = document.createElement("a"); 473 | anchor.classList.add(_Card.attr.CLASS_FULL_SCREEN_EXIT); 474 | anchor.tabIndex = 0; 475 | anchor.onclick = () => this.exitFullScreen(); 476 | anchor.onkeydown = (ev) => { 477 | if (ev.key === "Enter" || ev.key === " ") { 478 | this.exitFullScreen(); 479 | } 480 | }; 481 | anchor.innerHTML = this._overlayCloseHtml(); 482 | return anchor; 483 | } 484 | /** 485 | * Returns the HTML for the close icon. 486 | * @private 487 | * @returns {string} 488 | */ 489 | _overlayCloseHtml() { 490 | return "Close "; 491 | } 492 | /** 493 | * Returns the card instance associated with the given element, if any. 494 | * @public 495 | * @static 496 | * @param {HTMLElement} el 497 | * @returns {(Card | undefined)} 498 | */ 499 | static getInstance(el) { 500 | return _Card.instanceMap.get(el); 501 | } 502 | /** 503 | * Initializes all cards that require initialization on the page, or schedules 504 | * initialization if the DOM is not yet ready. 505 | * @public 506 | * @static 507 | * @param {boolean} [flushResizeObserver=true] 508 | */ 509 | static initializeAllCards(flushResizeObserver = true) { 510 | if (document.readyState === "loading") { 511 | if (!_Card.onReadyScheduled) { 512 | _Card.onReadyScheduled = true; 513 | document.addEventListener("DOMContentLoaded", () => { 514 | _Card.initializeAllCards(false); 515 | }); 516 | } 517 | return; 518 | } 519 | if (flushResizeObserver) { 520 | _Card.shinyResizeObserver.flush(); 521 | } 522 | const initSelector = `.${_Card.attr.CLASS_CARD}[${_Card.attr.ATTR_INIT}]`; 523 | if (!document.querySelector(initSelector)) { 524 | return; 525 | } 526 | const cards = document.querySelectorAll(initSelector); 527 | cards.forEach((card) => new _Card(card)); 528 | } 529 | }; 530 | var Card = _Card; 531 | /** 532 | * Key bslib-specific classes and attributes used by the card component. 533 | * @private 534 | * @static 535 | * @type {{ ATTR_INIT: string; CLASS_CARD: string; CLASS_FULL_SCREEN: string; CLASS_HAS_FULL_SCREEN: string; CLASS_FULL_SCREEN_ENTER: string; CLASS_FULL_SCREEN_EXIT: string; ID_FULL_SCREEN_OVERLAY: string; }} 536 | */ 537 | Card.attr = { 538 | // eslint-disable-next-line @typescript-eslint/naming-convention 539 | ATTR_INIT: "data-bslib-card-init", 540 | // eslint-disable-next-line @typescript-eslint/naming-convention 541 | CLASS_CARD: "bslib-card", 542 | // eslint-disable-next-line @typescript-eslint/naming-convention 543 | ATTR_FULL_SCREEN: "data-full-screen", 544 | // eslint-disable-next-line @typescript-eslint/naming-convention 545 | CLASS_HAS_FULL_SCREEN: "bslib-has-full-screen", 546 | // eslint-disable-next-line @typescript-eslint/naming-convention 547 | CLASS_FULL_SCREEN_ENTER: "bslib-full-screen-enter", 548 | // eslint-disable-next-line @typescript-eslint/naming-convention 549 | CLASS_FULL_SCREEN_EXIT: "bslib-full-screen-exit", 550 | // eslint-disable-next-line @typescript-eslint/naming-convention 551 | ID_FULL_SCREEN_OVERLAY: "bslib-full-screen-overlay" 552 | }; 553 | /** 554 | * A Shiny-specific resize observer that ensures Shiny outputs in within the 555 | * card resize appropriately. 556 | * @private 557 | * @type {ShinyResizeObserver} 558 | * @static 559 | */ 560 | Card.shinyResizeObserver = new ShinyResizeObserver(); 561 | /** 562 | * The registry of card instances and their associated DOM elements. 563 | * @private 564 | * @static 565 | * @type {WeakMap} 566 | */ 567 | Card.instanceMap = /* @__PURE__ */ new WeakMap(); 568 | /** 569 | * If cards are initialized before the DOM is ready, we re-schedule the 570 | * initialization to occur on DOMContentLoaded. 571 | * @private 572 | * @static 573 | * @type {boolean} 574 | */ 575 | Card.onReadyScheduled = false; 576 | window.bslib = window.bslib || {}; 577 | window.bslib.Card = Card; 578 | 579 | // srcts/src/components/sidebar.ts 580 | var _Sidebar = class { 581 | /** 582 | * Creates an instance of a collapsible bslib Sidebar. 583 | * @constructor 584 | * @param {HTMLElement} container 585 | */ 586 | constructor(container) { 587 | var _a; 588 | _Sidebar.instanceMap.set(container, this); 589 | this.layout = { 590 | container, 591 | main: container.querySelector(":scope > .main"), 592 | sidebar: container.querySelector(":scope > .sidebar"), 593 | toggle: container.querySelector( 594 | ":scope > .collapse-toggle" 595 | ) 596 | }; 597 | const sideAccordion = this.layout.sidebar.querySelector( 598 | ":scope > .sidebar-content > .accordion" 599 | ); 600 | if (sideAccordion) { 601 | (_a = sideAccordion == null ? void 0 : sideAccordion.parentElement) == null ? void 0 : _a.classList.add("has-accordion"); 602 | sideAccordion.classList.add("accordion-flush"); 603 | } 604 | if (this.layout.toggle) { 605 | this._initEventListeners(); 606 | this._initSidebarCounters(); 607 | this._initDesktop(); 608 | } 609 | _Sidebar.shinyResizeObserver.observe(this.layout.main); 610 | container.removeAttribute("data-bslib-sidebar-init"); 611 | const initScript = container.querySelector( 612 | ":scope > script[data-bslib-sidebar-init]" 613 | ); 614 | if (initScript) { 615 | container.removeChild(initScript); 616 | } 617 | } 618 | /** 619 | * Read the current state of the sidebar. Note that, when calling this method, 620 | * the sidebar may be transitioning into the state returned by this method. 621 | * 622 | * @description 623 | * The sidebar state works as follows, starting from the open state. When the 624 | * sidebar is closed: 625 | * 1. We add both the `COLLAPSE` and `TRANSITIONING` classes to the sidebar. 626 | * 2. The sidebar collapse begins to animate. On desktop devices, and where it 627 | * is supported, we transition the `grid-template-columns` property of the 628 | * sidebar layout. On mobile, the sidebar is hidden immediately. In both 629 | * cases, the collapse icon rotates and we use this rotation to determine 630 | * when the transition is complete. 631 | * 3. If another sidebar state toggle is requested while closing the sidebar, 632 | * we remove the `COLLAPSE` class and the animation immediately starts to 633 | * reverse. 634 | * 4. When the `transition` is complete, we remove the `TRANSITIONING` class. 635 | * @readonly 636 | * @type {boolean} 637 | */ 638 | get isClosed() { 639 | return this.layout.container.classList.contains(_Sidebar.classes.COLLAPSE); 640 | } 641 | /** 642 | * Given a sidebar container, return the Sidebar instance associated with it. 643 | * @public 644 | * @static 645 | * @param {HTMLElement} el 646 | * @returns {(Sidebar | undefined)} 647 | */ 648 | static getInstance(el) { 649 | return _Sidebar.instanceMap.get(el); 650 | } 651 | /** 652 | * Initialize all collapsible sidebars on the page. 653 | * @public 654 | * @static 655 | * @param {boolean} [flushResizeObserver=true] When `true`, we remove 656 | * non-existent elements from the ResizeObserver. This is required 657 | * periodically to prevent memory leaks. To avoid over-checking, we only flush 658 | * the ResizeObserver when initializing sidebars after page load. 659 | */ 660 | static initCollapsibleAll(flushResizeObserver = true) { 661 | if (document.readyState === "loading") { 662 | if (!_Sidebar.onReadyScheduled) { 663 | _Sidebar.onReadyScheduled = true; 664 | document.addEventListener("DOMContentLoaded", () => { 665 | _Sidebar.initCollapsibleAll(false); 666 | }); 667 | } 668 | return; 669 | } 670 | const initSelector = `.${_Sidebar.classes.LAYOUT}[data-bslib-sidebar-init]`; 671 | if (!document.querySelector(initSelector)) { 672 | return; 673 | } 674 | if (flushResizeObserver) 675 | _Sidebar.shinyResizeObserver.flush(); 676 | const containers = document.querySelectorAll(initSelector); 677 | containers.forEach((container) => new _Sidebar(container)); 678 | } 679 | /** 680 | * Initialize event listeners for the sidebar toggle button. 681 | * @private 682 | */ 683 | _initEventListeners() { 684 | var _a; 685 | const { toggle } = this.layout; 686 | toggle.addEventListener("click", (ev) => { 687 | ev.preventDefault(); 688 | this.toggle("toggle"); 689 | }); 690 | (_a = toggle.querySelector(".collapse-icon")) == null ? void 0 : _a.addEventListener("transitionend", () => this._finalizeState()); 691 | } 692 | /** 693 | * Initialize nested sidebar counters. 694 | * 695 | * @description 696 | * This function walks up the DOM tree, adding CSS variables to each direct 697 | * parent sidebar layout that count the layout's position in the stack of 698 | * nested layouts. We use these counters to keep the collapse toggles from 699 | * overlapping. Note that always-open sidebars that don't have collapse 700 | * toggles break the chain of nesting. 701 | * @private 702 | */ 703 | _initSidebarCounters() { 704 | const { container } = this.layout; 705 | const selectorChildLayouts = `.${_Sidebar.classes.LAYOUT}> .main > .${_Sidebar.classes.LAYOUT}:not([data-bslib-sidebar-open="always"])`; 706 | const isInnermostLayout = container.querySelector(selectorChildLayouts) === null; 707 | if (!isInnermostLayout) { 708 | return; 709 | } 710 | function nextSidebarParent(el) { 711 | el = el ? el.parentElement : null; 712 | if (el && el.classList.contains("main")) { 713 | el = el.parentElement; 714 | } 715 | if (el && el.classList.contains(_Sidebar.classes.LAYOUT)) { 716 | return el; 717 | } 718 | return null; 719 | } 720 | const layouts = [container]; 721 | let parent = nextSidebarParent(container); 722 | while (parent) { 723 | layouts.unshift(parent); 724 | parent = nextSidebarParent(parent); 725 | } 726 | const count = { left: 0, right: 0 }; 727 | layouts.forEach(function(x, i) { 728 | x.style.setProperty("--bslib-sidebar-counter", i.toString()); 729 | const isRight = x.classList.contains("sidebar-right"); 730 | const thisCount = isRight ? count.right++ : count.left++; 731 | x.style.setProperty( 732 | "--bslib-sidebar-overlap-counter", 733 | thisCount.toString() 734 | ); 735 | }); 736 | } 737 | /** 738 | * Initialize the sidebar's initial state when `open = "desktop"`. 739 | * @private 740 | */ 741 | _initDesktop() { 742 | var _a; 743 | const { container } = this.layout; 744 | if (((_a = container.dataset.bslibSidebarOpen) == null ? void 0 : _a.trim()) !== "desktop") { 745 | return; 746 | } 747 | const initCollapsed = window.getComputedStyle(container).getPropertyValue("--bslib-sidebar-js-init-collapsed"); 748 | if (initCollapsed.trim() === "true") { 749 | this.toggle("close"); 750 | } 751 | } 752 | /** 753 | * Toggle the sidebar's open/closed state. 754 | * @public 755 | * @param {SidebarToggleMethod | undefined} method Whether to `"open"`, 756 | * `"close"` or `"toggle"` the sidebar. If `.toggle()` is called without an 757 | * argument, it will toggle the sidebar's state. 758 | */ 759 | toggle(method) { 760 | if (typeof method === "undefined") { 761 | method = "toggle"; 762 | } 763 | const { container, sidebar } = this.layout; 764 | const isClosed = this.isClosed; 765 | if (["open", "close", "toggle"].indexOf(method) === -1) { 766 | throw new Error(`Unknown method ${method}`); 767 | } 768 | if (method === "toggle") { 769 | method = isClosed ? "open" : "close"; 770 | } 771 | if (isClosed && method === "close" || !isClosed && method === "open") { 772 | return; 773 | } 774 | if (method === "open") { 775 | sidebar.hidden = false; 776 | } 777 | container.classList.add(_Sidebar.classes.TRANSITIONING); 778 | container.classList.toggle(_Sidebar.classes.COLLAPSE); 779 | } 780 | /** 781 | * When the sidebar open/close transition ends, finalize the sidebar's state. 782 | * @private 783 | */ 784 | _finalizeState() { 785 | const { container, sidebar, toggle } = this.layout; 786 | container.classList.remove(_Sidebar.classes.TRANSITIONING); 787 | sidebar.hidden = this.isClosed; 788 | toggle.setAttribute("aria-expanded", this.isClosed ? "false" : "true"); 789 | const event = new CustomEvent("bslib.sidebar", { 790 | bubbles: true, 791 | detail: { open: !this.isClosed } 792 | }); 793 | sidebar.dispatchEvent(event); 794 | $(sidebar).trigger("toggleCollapse.sidebarInputBinding"); 795 | $(sidebar).trigger(this.isClosed ? "hidden" : "shown"); 796 | } 797 | }; 798 | var Sidebar = _Sidebar; 799 | /** 800 | * A Shiny-specific resize observer that ensures Shiny outputs in the main 801 | * content areas of the sidebar resize appropriately. 802 | * @private 803 | * @type {ShinyResizeObserver} 804 | * @static 805 | */ 806 | Sidebar.shinyResizeObserver = new ShinyResizeObserver(); 807 | /** 808 | * Static classes related to the sidebar layout or state. 809 | * @public 810 | * @static 811 | * @readonly 812 | * @type {{ LAYOUT: string; COLLAPSE: string; TRANSITIONING: string; }} 813 | */ 814 | Sidebar.classes = { 815 | // eslint-disable-next-line @typescript-eslint/naming-convention 816 | LAYOUT: "bslib-sidebar-layout", 817 | // eslint-disable-next-line @typescript-eslint/naming-convention 818 | COLLAPSE: "sidebar-collapsed", 819 | // eslint-disable-next-line @typescript-eslint/naming-convention 820 | TRANSITIONING: "transitioning" 821 | }; 822 | /** 823 | * If sidebars are initialized before the DOM is ready, we re-schedule the 824 | * initialization to occur on DOMContentLoaded. 825 | * @private 826 | * @static 827 | * @type {boolean} 828 | */ 829 | Sidebar.onReadyScheduled = false; 830 | /** 831 | * A map of initialized sidebars to their respective Sidebar instances. 832 | * @private 833 | * @static 834 | * @type {WeakMap} 835 | */ 836 | Sidebar.instanceMap = /* @__PURE__ */ new WeakMap(); 837 | var SidebarInputBinding = class extends InputBinding { 838 | find(scope) { 839 | return $(scope).find(`.${Sidebar.classes.LAYOUT} > .bslib-sidebar-input`); 840 | } 841 | getValue(el) { 842 | const sb = Sidebar.getInstance(el.parentElement); 843 | if (!sb) 844 | return false; 845 | return !sb.isClosed; 846 | } 847 | setValue(el, value) { 848 | const method = value ? "open" : "close"; 849 | this.receiveMessage(el, { method }); 850 | } 851 | subscribe(el, callback) { 852 | $(el).on( 853 | "toggleCollapse.sidebarInputBinding", 854 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 855 | function(event) { 856 | callback(true); 857 | } 858 | ); 859 | } 860 | unsubscribe(el) { 861 | $(el).off(".sidebarInputBinding"); 862 | } 863 | receiveMessage(el, data) { 864 | const sb = Sidebar.getInstance(el.parentElement); 865 | if (sb) 866 | sb.toggle(data.method); 867 | } 868 | }; 869 | registerBinding(SidebarInputBinding, "sidebar"); 870 | window.bslib = window.bslib || {}; 871 | window.bslib.Sidebar = Sidebar; 872 | 873 | // srcts/src/components/_shinyAddCustomMessageHandlers.ts 874 | function shinyAddCustomMessageHandlers(handlers) { 875 | if (!window.Shiny) { 876 | return; 877 | } 878 | for (const [name, handler] of Object.entries(handlers)) { 879 | Shiny.addCustomMessageHandler(name, handler); 880 | } 881 | } 882 | 883 | // srcts/src/components/index.ts 884 | var bslibMessageHandlers = { 885 | // eslint-disable-next-line @typescript-eslint/naming-convention 886 | "bslib.toggle-input-binary": (msg) => { 887 | const el = document.getElementById(msg.id); 888 | if (!el) { 889 | console.warn("[bslib.toggle-input-binary] No element found", msg); 890 | } 891 | const binding = $(el).data("shiny-input-binding"); 892 | if (!(binding instanceof InputBinding)) { 893 | console.warn("[bslib.toggle-input-binary] No input binding found", msg); 894 | return; 895 | } 896 | let value = msg.value; 897 | if (typeof value === "undefined") { 898 | value = !binding.getValue(el); 899 | } 900 | binding.receiveMessage(el, { value }); 901 | } 902 | }; 903 | if (window.Shiny) { 904 | shinyAddCustomMessageHandlers(bslibMessageHandlers); 905 | } 906 | function insertSvgGradient() { 907 | const temp = document.createElement("div"); 908 | temp.innerHTML = ` 909 | `; 921 | document.body.appendChild(temp.children[0]); 922 | } 923 | if (document.readyState === "complete") { 924 | insertSvgGradient(); 925 | } else { 926 | document.addEventListener("DOMContentLoaded", insertSvgGradient); 927 | } 928 | })(); 929 | 930 | -------------------------------------------------------------------------------- /dashboard/dashboard_files/libs/quarto-dashboard/quarto-dashboard.js: -------------------------------------------------------------------------------- 1 | const fillDivClasseses = ["widget-subarea", "lm-Widget", "leaflet-container"]; 2 | 3 | function requiresFill(el) { 4 | if (el.tagName === "DIV") { 5 | return fillDivClasseses.some((cls) => { 6 | return el.classList.contains(cls); 7 | }); 8 | } 9 | return false; 10 | } 11 | 12 | function ensureWidgetFills(el) { 13 | if (!el.classList.contains("html-fill-item")) { 14 | el.classList.add("html-fill-item"); 15 | } 16 | 17 | if (!el.classList.contains("html-fill-container")) { 18 | el.classList.add("html-fill-container"); 19 | } 20 | } 21 | 22 | function ensureWidgetsFill() { 23 | // Find any jupyter widget containers and keep an eye on them 24 | const widgetNodes = document.querySelectorAll(".widget-subarea"); 25 | for (const widgetEl of widgetNodes) { 26 | ensureWidgetFills(widgetEl); 27 | } 28 | } 29 | 30 | window.document.addEventListener("DOMContentLoaded", function (_event) { 31 | ensureWidgetsFill(); 32 | 33 | // Fixup any sharing links that require urls 34 | // Append url to any sharing urls 35 | const sharingLinks = window.document.querySelectorAll( 36 | "a.quarto-dashboard-link" 37 | ); 38 | for (let i = 0; i < sharingLinks.length; i++) { 39 | const sharingLink = sharingLinks[i]; 40 | const href = sharingLink.getAttribute("href"); 41 | if (href) { 42 | sharingLink.setAttribute( 43 | "href", 44 | href.replace("|url|", window.location.href) 45 | ); 46 | } 47 | } 48 | 49 | // Try to process the hash and activate a tab 50 | const hash = window.decodeURIComponent(window.location.hash); 51 | if (hash.length > 0) { 52 | QuartoDashboardUtils.showPage(hash); 53 | } 54 | 55 | // navigate to a tab when the history changes 56 | window.addEventListener("popstate", function (e) { 57 | const hash = window.decodeURIComponent(window.location.hash); 58 | QuartoDashboardUtils.showPage(hash); 59 | }); 60 | 61 | // Hook tabs and use that to update history / active tabs 62 | const navItems = document.querySelectorAll(".navbar .nav-item .nav-link"); 63 | for (const navItem of navItems) { 64 | const linkHref = navItem.getAttribute("href"); 65 | navItem.addEventListener("click", () => { 66 | const baseUrl = QuartoDashboardUtils.urlWithoutHash(window.location.href); 67 | const hash = QuartoDashboardUtils.urlHash(linkHref); 68 | const href = baseUrl + hash; 69 | QuartoDashboardUtils.setLocation(href); 70 | return false; 71 | }); 72 | } 73 | 74 | // Hook links in the body so users can link to pages 75 | const linkEls = document.querySelectorAll( 76 | ".quarto-dashboard-content a:not(.nav-link)" 77 | ); 78 | for (const linkEl of linkEls) { 79 | const linkHref = linkEl.getAttribute("href"); 80 | linkEl.addEventListener("click", () => { 81 | QuartoDashboardUtils.showPage(linkHref); 82 | return false; 83 | }); 84 | } 85 | const sidebar = window.document.querySelector( 86 | ".quarto-dashboard-content .bslib-sidebar-layout" 87 | ); 88 | let prevWidth = window.document.body.clientWidth; 89 | const sidebarCollapseClass = "sidebar-collapsed"; 90 | if (sidebar) { 91 | const resizeObserver = new ResizeObserver( 92 | throttle(function () { 93 | const clientWidth = window.document.body.clientWidth; 94 | if (prevWidth !== clientWidth) { 95 | if (clientWidth <= 576) { 96 | // Hide the sidebar 97 | if (!sidebar.classList.contains(sidebarCollapseClass)) { 98 | sidebar.classList.add(sidebarCollapseClass); 99 | } 100 | } else { 101 | // Show the sidebar 102 | if (sidebar.classList.contains(sidebarCollapseClass)) { 103 | sidebar.classList.remove(sidebarCollapseClass); 104 | } 105 | } 106 | prevWidth = clientWidth; 107 | } 108 | }, 2) 109 | ); 110 | resizeObserver.observe(window.document.body); 111 | } 112 | 113 | const observer = new MutationObserver(function (mutations) { 114 | mutations.forEach(function (mutation) { 115 | mutation.addedNodes.forEach(function (addedNode) { 116 | if (requiresFill(addedNode)) { 117 | ensureWidgetFills(addedNode); 118 | } 119 | }); 120 | }); 121 | }); 122 | observer.observe(document.body, { childList: true, subtree: true }); 123 | }); 124 | 125 | // utils 126 | window.QuartoDashboardUtils = { 127 | setLocation: function (href) { 128 | if (history && history.pushState) { 129 | history.pushState({}, null, href); 130 | // post "hashchange" for tools looking for it 131 | if (window.parent?.postMessage) { 132 | window.parent.postMessage( 133 | { 134 | type: "hashchange", 135 | href: window.location.href, 136 | }, 137 | "*" 138 | ); 139 | } 140 | } else { 141 | window.location.replace(href); 142 | } 143 | setTimeout(function () { 144 | window.scrollTo(0, 0); 145 | }, 10); 146 | }, 147 | isPage: function (hash) { 148 | const tabPaneEl = document.querySelector(`.dashboard-page.tab-pane${hash}`); 149 | return tabPaneEl !== null; 150 | }, 151 | showPage: function (hash) { 152 | // If the hash is empty, just select the first tab and activate that 153 | if (hash === "") { 154 | const firstTabPaneEl = document.querySelector(".dashboard-page.tab-pane"); 155 | if (firstTabPaneEl !== null) { 156 | hash = `#${firstTabPaneEl.id}`; 157 | } 158 | } 159 | 160 | // Find the tab and activate it 161 | const tabNodes = document.querySelectorAll(".navbar .nav-item .nav-link"); 162 | for (const tabEl of tabNodes) { 163 | const target = tabEl.getAttribute("data-bs-target"); 164 | if (target === hash) { 165 | tabEl.classList.add("active"); 166 | } else { 167 | tabEl.classList.remove("active"); 168 | } 169 | } 170 | 171 | // Find the tabpanes and activate the hash tab 172 | const tabPaneNodes = document.querySelectorAll(".dashboard-page.tab-pane"); 173 | for (const tabPaneEl of tabPaneNodes) { 174 | if (`#${tabPaneEl.id}` === hash) { 175 | tabPaneEl.classList.add("active"); 176 | } else { 177 | tabPaneEl.classList.remove("active"); 178 | } 179 | } 180 | }, 181 | showLinkedValue: function (href) { 182 | // check for a page link 183 | if (this.isPage(href)) { 184 | this.showPage(href); 185 | } else { 186 | window.open(href); 187 | } 188 | }, 189 | urlWithoutHash: function (url) { 190 | const hashLoc = url.indexOf("#"); 191 | if (hashLoc != -1) return url.substring(0, hashLoc); 192 | else return url; 193 | }, 194 | urlHash: function (url) { 195 | const hashLoc = url.indexOf("#"); 196 | if (hashLoc != -1) return url.substring(hashLoc); 197 | else return ""; 198 | }, 199 | }; 200 | 201 | function throttle(func, wait) { 202 | let waiting = false; 203 | return function () { 204 | if (!waiting) { 205 | func.apply(this, arguments); 206 | waiting = true; 207 | setTimeout(function () { 208 | waiting = false; 209 | }, wait); 210 | } 211 | }; 212 | } 213 | -------------------------------------------------------------------------------- /dashboard/dashboard_files/libs/quarto-html/anchor.min.js: -------------------------------------------------------------------------------- 1 | // @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat 2 | // 3 | // AnchorJS - v5.0.0 - 2023-01-18 4 | // https://www.bryanbraun.com/anchorjs/ 5 | // Copyright (c) 2023 Bryan Braun; Licensed MIT 6 | // 7 | // @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat 8 | !function(A,e){"use strict";"function"==typeof define&&define.amd?define([],e):"object"==typeof module&&module.exports?module.exports=e():(A.AnchorJS=e(),A.anchors=new A.AnchorJS)}(globalThis,function(){"use strict";return function(A){function u(A){A.icon=Object.prototype.hasOwnProperty.call(A,"icon")?A.icon:"",A.visible=Object.prototype.hasOwnProperty.call(A,"visible")?A.visible:"hover",A.placement=Object.prototype.hasOwnProperty.call(A,"placement")?A.placement:"right",A.ariaLabel=Object.prototype.hasOwnProperty.call(A,"ariaLabel")?A.ariaLabel:"Anchor",A.class=Object.prototype.hasOwnProperty.call(A,"class")?A.class:"",A.base=Object.prototype.hasOwnProperty.call(A,"base")?A.base:"",A.truncate=Object.prototype.hasOwnProperty.call(A,"truncate")?Math.floor(A.truncate):64,A.titleText=Object.prototype.hasOwnProperty.call(A,"titleText")?A.titleText:""}function d(A){var e;if("string"==typeof A||A instanceof String)e=[].slice.call(document.querySelectorAll(A));else{if(!(Array.isArray(A)||A instanceof NodeList))throw new TypeError("The selector provided to AnchorJS was invalid.");e=[].slice.call(A)}return e}this.options=A||{},this.elements=[],u(this.options),this.add=function(A){var e,t,o,i,n,s,a,r,l,c,h,p=[];if(u(this.options),0!==(e=d(A=A||"h2, h3, h4, h5, h6")).length){for(null===document.head.querySelector("style.anchorjs")&&((A=document.createElement("style")).className="anchorjs",A.appendChild(document.createTextNode("")),void 0===(h=document.head.querySelector('[rel="stylesheet"],style'))?document.head.appendChild(A):document.head.insertBefore(A,h),A.sheet.insertRule(".anchorjs-link{opacity:0;text-decoration:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}",A.sheet.cssRules.length),A.sheet.insertRule(":hover>.anchorjs-link,.anchorjs-link:focus{opacity:1}",A.sheet.cssRules.length),A.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",A.sheet.cssRules.length),A.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',A.sheet.cssRules.length)),h=document.querySelectorAll("[id]"),t=[].map.call(h,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}}); 9 | // @license-end -------------------------------------------------------------------------------- /dashboard/dashboard_files/libs/quarto-html/popper.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @popperjs/core v2.11.7 - MIT License 3 | */ 4 | 5 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";function t(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function n(e){return e instanceof t(e).Element||e instanceof Element}function r(e){return e instanceof t(e).HTMLElement||e instanceof HTMLElement}function o(e){return"undefined"!=typeof ShadowRoot&&(e instanceof t(e).ShadowRoot||e instanceof ShadowRoot)}var i=Math.max,a=Math.min,s=Math.round;function f(){var e=navigator.userAgentData;return null!=e&&e.brands&&Array.isArray(e.brands)?e.brands.map((function(e){return e.brand+"/"+e.version})).join(" "):navigator.userAgent}function c(){return!/^((?!chrome|android).)*safari/i.test(f())}function p(e,o,i){void 0===o&&(o=!1),void 0===i&&(i=!1);var a=e.getBoundingClientRect(),f=1,p=1;o&&r(e)&&(f=e.offsetWidth>0&&s(a.width)/e.offsetWidth||1,p=e.offsetHeight>0&&s(a.height)/e.offsetHeight||1);var u=(n(e)?t(e):window).visualViewport,l=!c()&&i,d=(a.left+(l&&u?u.offsetLeft:0))/f,h=(a.top+(l&&u?u.offsetTop:0))/p,m=a.width/f,v=a.height/p;return{width:m,height:v,top:h,right:d+m,bottom:h+v,left:d,x:d,y:h}}function u(e){var n=t(e);return{scrollLeft:n.pageXOffset,scrollTop:n.pageYOffset}}function l(e){return e?(e.nodeName||"").toLowerCase():null}function d(e){return((n(e)?e.ownerDocument:e.document)||window.document).documentElement}function h(e){return p(d(e)).left+u(e).scrollLeft}function m(e){return t(e).getComputedStyle(e)}function v(e){var t=m(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function y(e,n,o){void 0===o&&(o=!1);var i,a,f=r(n),c=r(n)&&function(e){var t=e.getBoundingClientRect(),n=s(t.width)/e.offsetWidth||1,r=s(t.height)/e.offsetHeight||1;return 1!==n||1!==r}(n),m=d(n),y=p(e,c,o),g={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(f||!f&&!o)&&(("body"!==l(n)||v(m))&&(g=(i=n)!==t(i)&&r(i)?{scrollLeft:(a=i).scrollLeft,scrollTop:a.scrollTop}:u(i)),r(n)?((b=p(n,!0)).x+=n.clientLeft,b.y+=n.clientTop):m&&(b.x=h(m))),{x:y.left+g.scrollLeft-b.x,y:y.top+g.scrollTop-b.y,width:y.width,height:y.height}}function g(e){var t=p(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function b(e){return"html"===l(e)?e:e.assignedSlot||e.parentNode||(o(e)?e.host:null)||d(e)}function x(e){return["html","body","#document"].indexOf(l(e))>=0?e.ownerDocument.body:r(e)&&v(e)?e:x(b(e))}function w(e,n){var r;void 0===n&&(n=[]);var o=x(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),a=t(o),s=i?[a].concat(a.visualViewport||[],v(o)?o:[]):o,f=n.concat(s);return i?f:f.concat(w(b(s)))}function O(e){return["table","td","th"].indexOf(l(e))>=0}function j(e){return r(e)&&"fixed"!==m(e).position?e.offsetParent:null}function E(e){for(var n=t(e),i=j(e);i&&O(i)&&"static"===m(i).position;)i=j(i);return i&&("html"===l(i)||"body"===l(i)&&"static"===m(i).position)?n:i||function(e){var t=/firefox/i.test(f());if(/Trident/i.test(f())&&r(e)&&"fixed"===m(e).position)return null;var n=b(e);for(o(n)&&(n=n.host);r(n)&&["html","body"].indexOf(l(n))<0;){var i=m(n);if("none"!==i.transform||"none"!==i.perspective||"paint"===i.contain||-1!==["transform","perspective"].indexOf(i.willChange)||t&&"filter"===i.willChange||t&&i.filter&&"none"!==i.filter)return n;n=n.parentNode}return null}(e)||n}var D="top",A="bottom",L="right",P="left",M="auto",k=[D,A,L,P],W="start",B="end",H="viewport",T="popper",R=k.reduce((function(e,t){return e.concat([t+"-"+W,t+"-"+B])}),[]),S=[].concat(k,[M]).reduce((function(e,t){return e.concat([t,t+"-"+W,t+"-"+B])}),[]),V=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function q(e){var t=new Map,n=new Set,r=[];function o(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!n.has(e)){var r=t.get(e);r&&o(r)}})),r.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||o(e)})),r}function C(e){return e.split("-")[0]}function N(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&o(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function I(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function _(e,r,o){return r===H?I(function(e,n){var r=t(e),o=d(e),i=r.visualViewport,a=o.clientWidth,s=o.clientHeight,f=0,p=0;if(i){a=i.width,s=i.height;var u=c();(u||!u&&"fixed"===n)&&(f=i.offsetLeft,p=i.offsetTop)}return{width:a,height:s,x:f+h(e),y:p}}(e,o)):n(r)?function(e,t){var n=p(e,!1,"fixed"===t);return n.top=n.top+e.clientTop,n.left=n.left+e.clientLeft,n.bottom=n.top+e.clientHeight,n.right=n.left+e.clientWidth,n.width=e.clientWidth,n.height=e.clientHeight,n.x=n.left,n.y=n.top,n}(r,o):I(function(e){var t,n=d(e),r=u(e),o=null==(t=e.ownerDocument)?void 0:t.body,a=i(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),s=i(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),f=-r.scrollLeft+h(e),c=-r.scrollTop;return"rtl"===m(o||n).direction&&(f+=i(n.clientWidth,o?o.clientWidth:0)-a),{width:a,height:s,x:f,y:c}}(d(e)))}function F(e,t,o,s){var f="clippingParents"===t?function(e){var t=w(b(e)),o=["absolute","fixed"].indexOf(m(e).position)>=0&&r(e)?E(e):e;return n(o)?t.filter((function(e){return n(e)&&N(e,o)&&"body"!==l(e)})):[]}(e):[].concat(t),c=[].concat(f,[o]),p=c[0],u=c.reduce((function(t,n){var r=_(e,n,s);return t.top=i(r.top,t.top),t.right=a(r.right,t.right),t.bottom=a(r.bottom,t.bottom),t.left=i(r.left,t.left),t}),_(e,p,s));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function U(e){return e.split("-")[1]}function z(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function X(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?C(o):null,a=o?U(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case D:t={x:s,y:n.y-r.height};break;case A:t={x:s,y:n.y+n.height};break;case L:t={x:n.x+n.width,y:f};break;case P:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?z(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case W:t[c]=t[c]-(n[p]/2-r[p]/2);break;case B:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}function Y(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function G(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function J(e,t){void 0===t&&(t={});var r=t,o=r.placement,i=void 0===o?e.placement:o,a=r.strategy,s=void 0===a?e.strategy:a,f=r.boundary,c=void 0===f?"clippingParents":f,u=r.rootBoundary,l=void 0===u?H:u,h=r.elementContext,m=void 0===h?T:h,v=r.altBoundary,y=void 0!==v&&v,g=r.padding,b=void 0===g?0:g,x=Y("number"!=typeof b?b:G(b,k)),w=m===T?"reference":T,O=e.rects.popper,j=e.elements[y?w:m],E=F(n(j)?j:j.contextElement||d(e.elements.popper),c,l,s),P=p(e.elements.reference),M=X({reference:P,element:O,strategy:"absolute",placement:i}),W=I(Object.assign({},O,M)),B=m===T?W:P,R={top:E.top-B.top+x.top,bottom:B.bottom-E.bottom+x.bottom,left:E.left-B.left+x.left,right:B.right-E.right+x.right},S=e.modifiersData.offset;if(m===T&&S){var V=S[i];Object.keys(R).forEach((function(e){var t=[L,A].indexOf(e)>=0?1:-1,n=[D,A].indexOf(e)>=0?"y":"x";R[e]+=V[n]*t}))}return R}var K={placement:"bottom",modifiers:[],strategy:"absolute"};function Q(){for(var e=arguments.length,t=new Array(e),n=0;n=0?-1:1,i="function"==typeof n?n(Object.assign({},t,{placement:e})):n,a=i[0],s=i[1];return a=a||0,s=(s||0)*o,[P,L].indexOf(r)>=0?{x:s,y:a}:{x:a,y:s}}(n,t.rects,i),e}),{}),s=a[t.placement],f=s.x,c=s.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=f,t.modifiersData.popperOffsets.y+=c),t.modifiersData[r]=a}},se={left:"right",right:"left",bottom:"top",top:"bottom"};function fe(e){return e.replace(/left|right|bottom|top/g,(function(e){return se[e]}))}var ce={start:"end",end:"start"};function pe(e){return e.replace(/start|end/g,(function(e){return ce[e]}))}function ue(e,t){void 0===t&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,s=n.flipVariations,f=n.allowedAutoPlacements,c=void 0===f?S:f,p=U(r),u=p?s?R:R.filter((function(e){return U(e)===p})):k,l=u.filter((function(e){return c.indexOf(e)>=0}));0===l.length&&(l=u);var d=l.reduce((function(t,n){return t[n]=J(e,{placement:n,boundary:o,rootBoundary:i,padding:a})[C(n)],t}),{});return Object.keys(d).sort((function(e,t){return d[e]-d[t]}))}var le={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=void 0===o||o,a=n.altAxis,s=void 0===a||a,f=n.fallbackPlacements,c=n.padding,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.flipVariations,h=void 0===d||d,m=n.allowedAutoPlacements,v=t.options.placement,y=C(v),g=f||(y===v||!h?[fe(v)]:function(e){if(C(e)===M)return[];var t=fe(e);return[pe(e),t,pe(t)]}(v)),b=[v].concat(g).reduce((function(e,n){return e.concat(C(n)===M?ue(t,{placement:n,boundary:p,rootBoundary:u,padding:c,flipVariations:h,allowedAutoPlacements:m}):n)}),[]),x=t.rects.reference,w=t.rects.popper,O=new Map,j=!0,E=b[0],k=0;k=0,S=R?"width":"height",V=J(t,{placement:B,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),q=R?T?L:P:T?A:D;x[S]>w[S]&&(q=fe(q));var N=fe(q),I=[];if(i&&I.push(V[H]<=0),s&&I.push(V[q]<=0,V[N]<=0),I.every((function(e){return e}))){E=B,j=!1;break}O.set(B,I)}if(j)for(var _=function(e){var t=b.find((function(t){var n=O.get(t);if(n)return n.slice(0,e).every((function(e){return e}))}));if(t)return E=t,"break"},F=h?3:1;F>0;F--){if("break"===_(F))break}t.placement!==E&&(t.modifiersData[r]._skip=!0,t.placement=E,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function de(e,t,n){return i(e,a(t,n))}var he={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,s=void 0===o||o,f=n.altAxis,c=void 0!==f&&f,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.padding,h=n.tether,m=void 0===h||h,v=n.tetherOffset,y=void 0===v?0:v,b=J(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),x=C(t.placement),w=U(t.placement),O=!w,j=z(x),M="x"===j?"y":"x",k=t.modifiersData.popperOffsets,B=t.rects.reference,H=t.rects.popper,T="function"==typeof y?y(Object.assign({},t.rects,{placement:t.placement})):y,R="number"==typeof T?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),S=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,V={x:0,y:0};if(k){if(s){var q,N="y"===j?D:P,I="y"===j?A:L,_="y"===j?"height":"width",F=k[j],X=F+b[N],Y=F-b[I],G=m?-H[_]/2:0,K=w===W?B[_]:H[_],Q=w===W?-H[_]:-B[_],Z=t.elements.arrow,$=m&&Z?g(Z):{width:0,height:0},ee=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=ee[N],ne=ee[I],re=de(0,B[_],$[_]),oe=O?B[_]/2-G-re-te-R.mainAxis:K-re-te-R.mainAxis,ie=O?-B[_]/2+G+re+ne+R.mainAxis:Q+re+ne+R.mainAxis,ae=t.elements.arrow&&E(t.elements.arrow),se=ae?"y"===j?ae.clientTop||0:ae.clientLeft||0:0,fe=null!=(q=null==S?void 0:S[j])?q:0,ce=F+ie-fe,pe=de(m?a(X,F+oe-fe-se):X,F,m?i(Y,ce):Y);k[j]=pe,V[j]=pe-F}if(c){var ue,le="x"===j?D:P,he="x"===j?A:L,me=k[M],ve="y"===M?"height":"width",ye=me+b[le],ge=me-b[he],be=-1!==[D,P].indexOf(x),xe=null!=(ue=null==S?void 0:S[M])?ue:0,we=be?ye:me-B[ve]-H[ve]-xe+R.altAxis,Oe=be?me+B[ve]+H[ve]-xe-R.altAxis:ge,je=m&&be?function(e,t,n){var r=de(e,t,n);return r>n?n:r}(we,me,Oe):de(m?we:ye,me,m?Oe:ge);k[M]=je,V[M]=je-me}t.modifiersData[r]=V}},requiresIfExists:["offset"]};var me={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,s=C(n.placement),f=z(s),c=[P,L].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return Y("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:G(e,k))}(o.padding,n),u=g(i),l="y"===f?D:P,d="y"===f?A:L,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],v=E(i),y=v?"y"===f?v.clientHeight||0:v.clientWidth||0:0,b=h/2-m/2,x=p[l],w=y-u[c]-p[d],O=y/2-u[c]/2+b,j=de(x,O,w),M=f;n.modifiersData[r]=((t={})[M]=j,t.centerOffset=j-O,t)}},effect:function(e){var t=e.state,n=e.options.element,r=void 0===n?"[data-popper-arrow]":n;null!=r&&("string"!=typeof r||(r=t.elements.popper.querySelector(r)))&&N(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ve(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function ye(e){return[D,L,A,P].some((function(t){return e[t]>=0}))}var ge={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=J(t,{elementContext:"reference"}),s=J(t,{altBoundary:!0}),f=ve(a,r),c=ve(s,o,i),p=ye(f),u=ye(c);t.modifiersData[n]={referenceClippingOffsets:f,popperEscapeOffsets:c,isReferenceHidden:p,hasPopperEscaped:u},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":p,"data-popper-escaped":u})}},be=Z({defaultModifiers:[ee,te,oe,ie]}),xe=[ee,te,oe,ie,ae,le,he,me,ge],we=Z({defaultModifiers:xe});e.applyStyles=ie,e.arrow=me,e.computeStyles=oe,e.createPopper=we,e.createPopperLite=be,e.defaultModifiers=xe,e.detectOverflow=J,e.eventListeners=ee,e.flip=le,e.hide=ge,e.offset=ae,e.popperGenerator=Z,e.popperOffsets=te,e.preventOverflow=he,Object.defineProperty(e,"__esModule",{value:!0})})); 6 | 7 | -------------------------------------------------------------------------------- /dashboard/dashboard_files/libs/quarto-html/quarto-syntax-highlighting.css: -------------------------------------------------------------------------------- 1 | /* quarto syntax highlight colors */ 2 | :root { 3 | --quarto-hl-ot-color: #003B4F; 4 | --quarto-hl-at-color: #657422; 5 | --quarto-hl-ss-color: #20794D; 6 | --quarto-hl-an-color: #5E5E5E; 7 | --quarto-hl-fu-color: #4758AB; 8 | --quarto-hl-st-color: #20794D; 9 | --quarto-hl-cf-color: #003B4F; 10 | --quarto-hl-op-color: #5E5E5E; 11 | --quarto-hl-er-color: #AD0000; 12 | --quarto-hl-bn-color: #AD0000; 13 | --quarto-hl-al-color: #AD0000; 14 | --quarto-hl-va-color: #111111; 15 | --quarto-hl-bu-color: inherit; 16 | --quarto-hl-ex-color: inherit; 17 | --quarto-hl-pp-color: #AD0000; 18 | --quarto-hl-in-color: #5E5E5E; 19 | --quarto-hl-vs-color: #20794D; 20 | --quarto-hl-wa-color: #5E5E5E; 21 | --quarto-hl-do-color: #5E5E5E; 22 | --quarto-hl-im-color: #00769E; 23 | --quarto-hl-ch-color: #20794D; 24 | --quarto-hl-dt-color: #AD0000; 25 | --quarto-hl-fl-color: #AD0000; 26 | --quarto-hl-co-color: #5E5E5E; 27 | --quarto-hl-cv-color: #5E5E5E; 28 | --quarto-hl-cn-color: #8f5902; 29 | --quarto-hl-sc-color: #5E5E5E; 30 | --quarto-hl-dv-color: #AD0000; 31 | --quarto-hl-kw-color: #003B4F; 32 | } 33 | 34 | /* other quarto variables */ 35 | :root { 36 | --quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 37 | } 38 | 39 | pre > code.sourceCode > span { 40 | color: #003B4F; 41 | } 42 | 43 | code span { 44 | color: #003B4F; 45 | } 46 | 47 | code.sourceCode > span { 48 | color: #003B4F; 49 | } 50 | 51 | div.sourceCode, 52 | div.sourceCode pre.sourceCode { 53 | color: #003B4F; 54 | } 55 | 56 | code span.ot { 57 | color: #003B4F; 58 | font-style: inherit; 59 | } 60 | 61 | code span.at { 62 | color: #657422; 63 | font-style: inherit; 64 | } 65 | 66 | code span.ss { 67 | color: #20794D; 68 | font-style: inherit; 69 | } 70 | 71 | code span.an { 72 | color: #5E5E5E; 73 | font-style: inherit; 74 | } 75 | 76 | code span.fu { 77 | color: #4758AB; 78 | font-style: inherit; 79 | } 80 | 81 | code span.st { 82 | color: #20794D; 83 | font-style: inherit; 84 | } 85 | 86 | code span.cf { 87 | color: #003B4F; 88 | font-style: inherit; 89 | } 90 | 91 | code span.op { 92 | color: #5E5E5E; 93 | font-style: inherit; 94 | } 95 | 96 | code span.er { 97 | color: #AD0000; 98 | font-style: inherit; 99 | } 100 | 101 | code span.bn { 102 | color: #AD0000; 103 | font-style: inherit; 104 | } 105 | 106 | code span.al { 107 | color: #AD0000; 108 | font-style: inherit; 109 | } 110 | 111 | code span.va { 112 | color: #111111; 113 | font-style: inherit; 114 | } 115 | 116 | code span.bu { 117 | font-style: inherit; 118 | } 119 | 120 | code span.ex { 121 | font-style: inherit; 122 | } 123 | 124 | code span.pp { 125 | color: #AD0000; 126 | font-style: inherit; 127 | } 128 | 129 | code span.in { 130 | color: #5E5E5E; 131 | font-style: inherit; 132 | } 133 | 134 | code span.vs { 135 | color: #20794D; 136 | font-style: inherit; 137 | } 138 | 139 | code span.wa { 140 | color: #5E5E5E; 141 | font-style: italic; 142 | } 143 | 144 | code span.do { 145 | color: #5E5E5E; 146 | font-style: italic; 147 | } 148 | 149 | code span.im { 150 | color: #00769E; 151 | font-style: inherit; 152 | } 153 | 154 | code span.ch { 155 | color: #20794D; 156 | font-style: inherit; 157 | } 158 | 159 | code span.dt { 160 | color: #AD0000; 161 | font-style: inherit; 162 | } 163 | 164 | code span.fl { 165 | color: #AD0000; 166 | font-style: inherit; 167 | } 168 | 169 | code span.co { 170 | color: #5E5E5E; 171 | font-style: inherit; 172 | } 173 | 174 | code span.cv { 175 | color: #5E5E5E; 176 | font-style: italic; 177 | } 178 | 179 | code span.cn { 180 | color: #8f5902; 181 | font-style: inherit; 182 | } 183 | 184 | code span.sc { 185 | color: #5E5E5E; 186 | font-style: inherit; 187 | } 188 | 189 | code span.dv { 190 | color: #AD0000; 191 | font-style: inherit; 192 | } 193 | 194 | code span.kw { 195 | color: #003B4F; 196 | font-style: inherit; 197 | } 198 | 199 | .prevent-inlining { 200 | content: " { 9 | // Find any conflicting margin elements and add margins to the 10 | // top to prevent overlap 11 | const marginChildren = window.document.querySelectorAll( 12 | ".column-margin.column-container > * " 13 | ); 14 | 15 | let lastBottom = 0; 16 | for (const marginChild of marginChildren) { 17 | if (marginChild.offsetParent !== null) { 18 | // clear the top margin so we recompute it 19 | marginChild.style.marginTop = null; 20 | const top = marginChild.getBoundingClientRect().top + window.scrollY; 21 | if (top < lastBottom) { 22 | const margin = lastBottom - top; 23 | marginChild.style.marginTop = `${margin}px`; 24 | } 25 | const styles = window.getComputedStyle(marginChild); 26 | const marginTop = parseFloat(styles["marginTop"]); 27 | lastBottom = top + marginChild.getBoundingClientRect().height + marginTop; 28 | } 29 | } 30 | }; 31 | 32 | window.document.addEventListener("DOMContentLoaded", function (_event) { 33 | // Recompute the position of margin elements anytime the body size changes 34 | if (window.ResizeObserver) { 35 | const resizeObserver = new window.ResizeObserver( 36 | throttle(layoutMarginEls, 50) 37 | ); 38 | resizeObserver.observe(window.document.body); 39 | } 40 | 41 | const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]'); 42 | const sidebarEl = window.document.getElementById("quarto-sidebar"); 43 | const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left"); 44 | const marginSidebarEl = window.document.getElementById( 45 | "quarto-margin-sidebar" 46 | ); 47 | // function to determine whether the element has a previous sibling that is active 48 | const prevSiblingIsActiveLink = (el) => { 49 | const sibling = el.previousElementSibling; 50 | if (sibling && sibling.tagName === "A") { 51 | return sibling.classList.contains("active"); 52 | } else { 53 | return false; 54 | } 55 | }; 56 | 57 | // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior) 58 | function fireSlideEnter(e) { 59 | const event = window.document.createEvent("Event"); 60 | event.initEvent("slideenter", true, true); 61 | window.document.dispatchEvent(event); 62 | } 63 | const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]'); 64 | tabs.forEach((tab) => { 65 | tab.addEventListener("shown.bs.tab", fireSlideEnter); 66 | }); 67 | 68 | // fire slideEnter for tabby tab activations (for htmlwidget resize behavior) 69 | document.addEventListener("tabby", fireSlideEnter, false); 70 | 71 | // Track scrolling and mark TOC links as active 72 | // get table of contents and sidebar (bail if we don't have at least one) 73 | const tocLinks = tocEl 74 | ? [...tocEl.querySelectorAll("a[data-scroll-target]")] 75 | : []; 76 | const makeActive = (link) => tocLinks[link].classList.add("active"); 77 | const removeActive = (link) => tocLinks[link].classList.remove("active"); 78 | const removeAllActive = () => 79 | [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link)); 80 | 81 | // activate the anchor for a section associated with this TOC entry 82 | tocLinks.forEach((link) => { 83 | link.addEventListener("click", () => { 84 | if (link.href.indexOf("#") !== -1) { 85 | const anchor = link.href.split("#")[1]; 86 | const heading = window.document.querySelector( 87 | `[data-anchor-id=${anchor}]` 88 | ); 89 | if (heading) { 90 | // Add the class 91 | heading.classList.add("reveal-anchorjs-link"); 92 | 93 | // function to show the anchor 94 | const handleMouseout = () => { 95 | heading.classList.remove("reveal-anchorjs-link"); 96 | heading.removeEventListener("mouseout", handleMouseout); 97 | }; 98 | 99 | // add a function to clear the anchor when the user mouses out of it 100 | heading.addEventListener("mouseout", handleMouseout); 101 | } 102 | } 103 | }); 104 | }); 105 | 106 | const sections = tocLinks.map((link) => { 107 | const target = link.getAttribute("data-scroll-target"); 108 | if (target.startsWith("#")) { 109 | return window.document.getElementById(decodeURI(`${target.slice(1)}`)); 110 | } else { 111 | return window.document.querySelector(decodeURI(`${target}`)); 112 | } 113 | }); 114 | 115 | const sectionMargin = 200; 116 | let currentActive = 0; 117 | // track whether we've initialized state the first time 118 | let init = false; 119 | 120 | const updateActiveLink = () => { 121 | // The index from bottom to top (e.g. reversed list) 122 | let sectionIndex = -1; 123 | if ( 124 | window.innerHeight + window.pageYOffset >= 125 | window.document.body.offsetHeight 126 | ) { 127 | sectionIndex = 0; 128 | } else { 129 | sectionIndex = [...sections].reverse().findIndex((section) => { 130 | if (section) { 131 | return window.pageYOffset >= section.offsetTop - sectionMargin; 132 | } else { 133 | return false; 134 | } 135 | }); 136 | } 137 | if (sectionIndex > -1) { 138 | const current = sections.length - sectionIndex - 1; 139 | if (current !== currentActive) { 140 | removeAllActive(); 141 | currentActive = current; 142 | makeActive(current); 143 | if (init) { 144 | window.dispatchEvent(sectionChanged); 145 | } 146 | init = true; 147 | } 148 | } 149 | }; 150 | 151 | const inHiddenRegion = (top, bottom, hiddenRegions) => { 152 | for (const region of hiddenRegions) { 153 | if (top <= region.bottom && bottom >= region.top) { 154 | return true; 155 | } 156 | } 157 | return false; 158 | }; 159 | 160 | const categorySelector = "header.quarto-title-block .quarto-category"; 161 | const activateCategories = (href) => { 162 | // Find any categories 163 | // Surround them with a link pointing back to: 164 | // #category=Authoring 165 | try { 166 | const categoryEls = window.document.querySelectorAll(categorySelector); 167 | for (const categoryEl of categoryEls) { 168 | const categoryText = categoryEl.textContent; 169 | if (categoryText) { 170 | const link = `${href}#category=${encodeURIComponent(categoryText)}`; 171 | const linkEl = window.document.createElement("a"); 172 | linkEl.setAttribute("href", link); 173 | for (const child of categoryEl.childNodes) { 174 | linkEl.append(child); 175 | } 176 | categoryEl.appendChild(linkEl); 177 | } 178 | } 179 | } catch { 180 | // Ignore errors 181 | } 182 | }; 183 | function hasTitleCategories() { 184 | return window.document.querySelector(categorySelector) !== null; 185 | } 186 | 187 | function offsetRelativeUrl(url) { 188 | const offset = getMeta("quarto:offset"); 189 | return offset ? offset + url : url; 190 | } 191 | 192 | function offsetAbsoluteUrl(url) { 193 | const offset = getMeta("quarto:offset"); 194 | const baseUrl = new URL(offset, window.location); 195 | 196 | const projRelativeUrl = url.replace(baseUrl, ""); 197 | if (projRelativeUrl.startsWith("/")) { 198 | return projRelativeUrl; 199 | } else { 200 | return "/" + projRelativeUrl; 201 | } 202 | } 203 | 204 | // read a meta tag value 205 | function getMeta(metaName) { 206 | const metas = window.document.getElementsByTagName("meta"); 207 | for (let i = 0; i < metas.length; i++) { 208 | if (metas[i].getAttribute("name") === metaName) { 209 | return metas[i].getAttribute("content"); 210 | } 211 | } 212 | return ""; 213 | } 214 | 215 | async function findAndActivateCategories() { 216 | const currentPagePath = offsetAbsoluteUrl(window.location.href); 217 | const response = await fetch(offsetRelativeUrl("listings.json")); 218 | if (response.status == 200) { 219 | return response.json().then(function (listingPaths) { 220 | const listingHrefs = []; 221 | for (const listingPath of listingPaths) { 222 | const pathWithoutLeadingSlash = listingPath.listing.substring(1); 223 | for (const item of listingPath.items) { 224 | if ( 225 | item === currentPagePath || 226 | item === currentPagePath + "index.html" 227 | ) { 228 | // Resolve this path against the offset to be sure 229 | // we already are using the correct path to the listing 230 | // (this adjusts the listing urls to be rooted against 231 | // whatever root the page is actually running against) 232 | const relative = offsetRelativeUrl(pathWithoutLeadingSlash); 233 | const baseUrl = window.location; 234 | const resolvedPath = new URL(relative, baseUrl); 235 | listingHrefs.push(resolvedPath.pathname); 236 | break; 237 | } 238 | } 239 | } 240 | 241 | // Look up the tree for a nearby linting and use that if we find one 242 | const nearestListing = findNearestParentListing( 243 | offsetAbsoluteUrl(window.location.pathname), 244 | listingHrefs 245 | ); 246 | if (nearestListing) { 247 | activateCategories(nearestListing); 248 | } else { 249 | // See if the referrer is a listing page for this item 250 | const referredRelativePath = offsetAbsoluteUrl(document.referrer); 251 | const referrerListing = listingHrefs.find((listingHref) => { 252 | const isListingReferrer = 253 | listingHref === referredRelativePath || 254 | listingHref === referredRelativePath + "index.html"; 255 | return isListingReferrer; 256 | }); 257 | 258 | if (referrerListing) { 259 | // Try to use the referrer if possible 260 | activateCategories(referrerListing); 261 | } else if (listingHrefs.length > 0) { 262 | // Otherwise, just fall back to the first listing 263 | activateCategories(listingHrefs[0]); 264 | } 265 | } 266 | }); 267 | } 268 | } 269 | if (hasTitleCategories()) { 270 | findAndActivateCategories(); 271 | } 272 | 273 | const findNearestParentListing = (href, listingHrefs) => { 274 | if (!href || !listingHrefs) { 275 | return undefined; 276 | } 277 | // Look up the tree for a nearby linting and use that if we find one 278 | const relativeParts = href.substring(1).split("/"); 279 | while (relativeParts.length > 0) { 280 | const path = relativeParts.join("/"); 281 | for (const listingHref of listingHrefs) { 282 | if (listingHref.startsWith(path)) { 283 | return listingHref; 284 | } 285 | } 286 | relativeParts.pop(); 287 | } 288 | 289 | return undefined; 290 | }; 291 | 292 | const manageSidebarVisiblity = (el, placeholderDescriptor) => { 293 | let isVisible = true; 294 | let elRect; 295 | 296 | return (hiddenRegions) => { 297 | if (el === null) { 298 | return; 299 | } 300 | 301 | // Find the last element of the TOC 302 | const lastChildEl = el.lastElementChild; 303 | 304 | if (lastChildEl) { 305 | // Converts the sidebar to a menu 306 | const convertToMenu = () => { 307 | for (const child of el.children) { 308 | child.style.opacity = 0; 309 | child.style.overflow = "hidden"; 310 | } 311 | 312 | nexttick(() => { 313 | const toggleContainer = window.document.createElement("div"); 314 | toggleContainer.style.width = "100%"; 315 | toggleContainer.classList.add("zindex-over-content"); 316 | toggleContainer.classList.add("quarto-sidebar-toggle"); 317 | toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom 318 | toggleContainer.id = placeholderDescriptor.id; 319 | toggleContainer.style.position = "fixed"; 320 | 321 | const toggleIcon = window.document.createElement("i"); 322 | toggleIcon.classList.add("quarto-sidebar-toggle-icon"); 323 | toggleIcon.classList.add("bi"); 324 | toggleIcon.classList.add("bi-caret-down-fill"); 325 | 326 | const toggleTitle = window.document.createElement("div"); 327 | const titleEl = window.document.body.querySelector( 328 | placeholderDescriptor.titleSelector 329 | ); 330 | if (titleEl) { 331 | toggleTitle.append( 332 | titleEl.textContent || titleEl.innerText, 333 | toggleIcon 334 | ); 335 | } 336 | toggleTitle.classList.add("zindex-over-content"); 337 | toggleTitle.classList.add("quarto-sidebar-toggle-title"); 338 | toggleContainer.append(toggleTitle); 339 | 340 | const toggleContents = window.document.createElement("div"); 341 | toggleContents.classList = el.classList; 342 | toggleContents.classList.add("zindex-over-content"); 343 | toggleContents.classList.add("quarto-sidebar-toggle-contents"); 344 | for (const child of el.children) { 345 | if (child.id === "toc-title") { 346 | continue; 347 | } 348 | 349 | const clone = child.cloneNode(true); 350 | clone.style.opacity = 1; 351 | clone.style.display = null; 352 | toggleContents.append(clone); 353 | } 354 | toggleContents.style.height = "0px"; 355 | const positionToggle = () => { 356 | // position the element (top left of parent, same width as parent) 357 | if (!elRect) { 358 | elRect = el.getBoundingClientRect(); 359 | } 360 | toggleContainer.style.left = `${elRect.left}px`; 361 | toggleContainer.style.top = `${elRect.top}px`; 362 | toggleContainer.style.width = `${elRect.width}px`; 363 | }; 364 | positionToggle(); 365 | 366 | toggleContainer.append(toggleContents); 367 | el.parentElement.prepend(toggleContainer); 368 | 369 | // Process clicks 370 | let tocShowing = false; 371 | // Allow the caller to control whether this is dismissed 372 | // when it is clicked (e.g. sidebar navigation supports 373 | // opening and closing the nav tree, so don't dismiss on click) 374 | const clickEl = placeholderDescriptor.dismissOnClick 375 | ? toggleContainer 376 | : toggleTitle; 377 | 378 | const closeToggle = () => { 379 | if (tocShowing) { 380 | toggleContainer.classList.remove("expanded"); 381 | toggleContents.style.height = "0px"; 382 | tocShowing = false; 383 | } 384 | }; 385 | 386 | // Get rid of any expanded toggle if the user scrolls 387 | window.document.addEventListener( 388 | "scroll", 389 | throttle(() => { 390 | closeToggle(); 391 | }, 50) 392 | ); 393 | 394 | // Handle positioning of the toggle 395 | window.addEventListener( 396 | "resize", 397 | throttle(() => { 398 | elRect = undefined; 399 | positionToggle(); 400 | }, 50) 401 | ); 402 | 403 | window.addEventListener("quarto-hrChanged", () => { 404 | elRect = undefined; 405 | }); 406 | 407 | // Process the click 408 | clickEl.onclick = () => { 409 | if (!tocShowing) { 410 | toggleContainer.classList.add("expanded"); 411 | toggleContents.style.height = null; 412 | tocShowing = true; 413 | } else { 414 | closeToggle(); 415 | } 416 | }; 417 | }); 418 | }; 419 | 420 | // Converts a sidebar from a menu back to a sidebar 421 | const convertToSidebar = () => { 422 | for (const child of el.children) { 423 | child.style.opacity = 1; 424 | child.style.overflow = null; 425 | } 426 | 427 | const placeholderEl = window.document.getElementById( 428 | placeholderDescriptor.id 429 | ); 430 | if (placeholderEl) { 431 | placeholderEl.remove(); 432 | } 433 | 434 | el.classList.remove("rollup"); 435 | }; 436 | 437 | if (isReaderMode()) { 438 | convertToMenu(); 439 | isVisible = false; 440 | } else { 441 | // Find the top and bottom o the element that is being managed 442 | const elTop = el.offsetTop; 443 | const elBottom = 444 | elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight; 445 | 446 | if (!isVisible) { 447 | // If the element is current not visible reveal if there are 448 | // no conflicts with overlay regions 449 | if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) { 450 | convertToSidebar(); 451 | isVisible = true; 452 | } 453 | } else { 454 | // If the element is visible, hide it if it conflicts with overlay regions 455 | // and insert a placeholder toggle (or if we're in reader mode) 456 | if (inHiddenRegion(elTop, elBottom, hiddenRegions)) { 457 | convertToMenu(); 458 | isVisible = false; 459 | } 460 | } 461 | } 462 | } 463 | }; 464 | }; 465 | 466 | const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]'); 467 | for (const tabEl of tabEls) { 468 | const id = tabEl.getAttribute("data-bs-target"); 469 | if (id) { 470 | const columnEl = document.querySelector( 471 | `${id} .column-margin, .tabset-margin-content` 472 | ); 473 | if (columnEl) 474 | tabEl.addEventListener("shown.bs.tab", function (event) { 475 | const el = event.srcElement; 476 | if (el) { 477 | const visibleCls = `${el.id}-margin-content`; 478 | // walk up until we find a parent tabset 479 | let panelTabsetEl = el.parentElement; 480 | while (panelTabsetEl) { 481 | if (panelTabsetEl.classList.contains("panel-tabset")) { 482 | break; 483 | } 484 | panelTabsetEl = panelTabsetEl.parentElement; 485 | } 486 | 487 | if (panelTabsetEl) { 488 | const prevSib = panelTabsetEl.previousElementSibling; 489 | if ( 490 | prevSib && 491 | prevSib.classList.contains("tabset-margin-container") 492 | ) { 493 | const childNodes = prevSib.querySelectorAll( 494 | ".tabset-margin-content" 495 | ); 496 | for (const childEl of childNodes) { 497 | if (childEl.classList.contains(visibleCls)) { 498 | childEl.classList.remove("collapse"); 499 | } else { 500 | childEl.classList.add("collapse"); 501 | } 502 | } 503 | } 504 | } 505 | } 506 | 507 | layoutMarginEls(); 508 | }); 509 | } 510 | } 511 | 512 | // Manage the visibility of the toc and the sidebar 513 | const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, { 514 | id: "quarto-toc-toggle", 515 | titleSelector: "#toc-title", 516 | dismissOnClick: true, 517 | }); 518 | const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, { 519 | id: "quarto-sidebarnav-toggle", 520 | titleSelector: ".title", 521 | dismissOnClick: false, 522 | }); 523 | let tocLeftScrollVisibility; 524 | if (leftTocEl) { 525 | tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, { 526 | id: "quarto-lefttoc-toggle", 527 | titleSelector: "#toc-title", 528 | dismissOnClick: true, 529 | }); 530 | } 531 | 532 | // Find the first element that uses formatting in special columns 533 | const conflictingEls = window.document.body.querySelectorAll( 534 | '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]' 535 | ); 536 | 537 | // Filter all the possibly conflicting elements into ones 538 | // the do conflict on the left or ride side 539 | const arrConflictingEls = Array.from(conflictingEls); 540 | const leftSideConflictEls = arrConflictingEls.filter((el) => { 541 | if (el.tagName === "ASIDE") { 542 | return false; 543 | } 544 | return Array.from(el.classList).find((className) => { 545 | return ( 546 | className !== "column-body" && 547 | className.startsWith("column-") && 548 | !className.endsWith("right") && 549 | !className.endsWith("container") && 550 | className !== "column-margin" 551 | ); 552 | }); 553 | }); 554 | const rightSideConflictEls = arrConflictingEls.filter((el) => { 555 | if (el.tagName === "ASIDE") { 556 | return true; 557 | } 558 | 559 | const hasMarginCaption = Array.from(el.classList).find((className) => { 560 | return className == "margin-caption"; 561 | }); 562 | if (hasMarginCaption) { 563 | return true; 564 | } 565 | 566 | return Array.from(el.classList).find((className) => { 567 | return ( 568 | className !== "column-body" && 569 | !className.endsWith("container") && 570 | className.startsWith("column-") && 571 | !className.endsWith("left") 572 | ); 573 | }); 574 | }); 575 | 576 | const kOverlapPaddingSize = 10; 577 | function toRegions(els) { 578 | return els.map((el) => { 579 | const boundRect = el.getBoundingClientRect(); 580 | const top = 581 | boundRect.top + 582 | document.documentElement.scrollTop - 583 | kOverlapPaddingSize; 584 | return { 585 | top, 586 | bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize, 587 | }; 588 | }); 589 | } 590 | 591 | let hasObserved = false; 592 | const visibleItemObserver = (els) => { 593 | let visibleElements = [...els]; 594 | const intersectionObserver = new IntersectionObserver( 595 | (entries, _observer) => { 596 | entries.forEach((entry) => { 597 | if (entry.isIntersecting) { 598 | if (visibleElements.indexOf(entry.target) === -1) { 599 | visibleElements.push(entry.target); 600 | } 601 | } else { 602 | visibleElements = visibleElements.filter((visibleEntry) => { 603 | return visibleEntry !== entry; 604 | }); 605 | } 606 | }); 607 | 608 | if (!hasObserved) { 609 | hideOverlappedSidebars(); 610 | } 611 | hasObserved = true; 612 | }, 613 | {} 614 | ); 615 | els.forEach((el) => { 616 | intersectionObserver.observe(el); 617 | }); 618 | 619 | return { 620 | getVisibleEntries: () => { 621 | return visibleElements; 622 | }, 623 | }; 624 | }; 625 | 626 | const rightElementObserver = visibleItemObserver(rightSideConflictEls); 627 | const leftElementObserver = visibleItemObserver(leftSideConflictEls); 628 | 629 | const hideOverlappedSidebars = () => { 630 | marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries())); 631 | sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries())); 632 | if (tocLeftScrollVisibility) { 633 | tocLeftScrollVisibility( 634 | toRegions(leftElementObserver.getVisibleEntries()) 635 | ); 636 | } 637 | }; 638 | 639 | window.quartoToggleReader = () => { 640 | // Applies a slow class (or removes it) 641 | // to update the transition speed 642 | const slowTransition = (slow) => { 643 | const manageTransition = (id, slow) => { 644 | const el = document.getElementById(id); 645 | if (el) { 646 | if (slow) { 647 | el.classList.add("slow"); 648 | } else { 649 | el.classList.remove("slow"); 650 | } 651 | } 652 | }; 653 | 654 | manageTransition("TOC", slow); 655 | manageTransition("quarto-sidebar", slow); 656 | }; 657 | const readerMode = !isReaderMode(); 658 | setReaderModeValue(readerMode); 659 | 660 | // If we're entering reader mode, slow the transition 661 | if (readerMode) { 662 | slowTransition(readerMode); 663 | } 664 | highlightReaderToggle(readerMode); 665 | hideOverlappedSidebars(); 666 | 667 | // If we're exiting reader mode, restore the non-slow transition 668 | if (!readerMode) { 669 | slowTransition(!readerMode); 670 | } 671 | }; 672 | 673 | const highlightReaderToggle = (readerMode) => { 674 | const els = document.querySelectorAll(".quarto-reader-toggle"); 675 | if (els) { 676 | els.forEach((el) => { 677 | if (readerMode) { 678 | el.classList.add("reader"); 679 | } else { 680 | el.classList.remove("reader"); 681 | } 682 | }); 683 | } 684 | }; 685 | 686 | const setReaderModeValue = (val) => { 687 | if (window.location.protocol !== "file:") { 688 | window.localStorage.setItem("quarto-reader-mode", val); 689 | } else { 690 | localReaderMode = val; 691 | } 692 | }; 693 | 694 | const isReaderMode = () => { 695 | if (window.location.protocol !== "file:") { 696 | return window.localStorage.getItem("quarto-reader-mode") === "true"; 697 | } else { 698 | return localReaderMode; 699 | } 700 | }; 701 | let localReaderMode = null; 702 | 703 | const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded"); 704 | const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1; 705 | 706 | // Walk the TOC and collapse/expand nodes 707 | // Nodes are expanded if: 708 | // - they are top level 709 | // - they have children that are 'active' links 710 | // - they are directly below an link that is 'active' 711 | const walk = (el, depth) => { 712 | // Tick depth when we enter a UL 713 | if (el.tagName === "UL") { 714 | depth = depth + 1; 715 | } 716 | 717 | // It this is active link 718 | let isActiveNode = false; 719 | if (el.tagName === "A" && el.classList.contains("active")) { 720 | isActiveNode = true; 721 | } 722 | 723 | // See if there is an active child to this element 724 | let hasActiveChild = false; 725 | for (child of el.children) { 726 | hasActiveChild = walk(child, depth) || hasActiveChild; 727 | } 728 | 729 | // Process the collapse state if this is an UL 730 | if (el.tagName === "UL") { 731 | if (tocOpenDepth === -1 && depth > 1) { 732 | el.classList.add("collapse"); 733 | } else if ( 734 | depth <= tocOpenDepth || 735 | hasActiveChild || 736 | prevSiblingIsActiveLink(el) 737 | ) { 738 | el.classList.remove("collapse"); 739 | } else { 740 | el.classList.add("collapse"); 741 | } 742 | 743 | // untick depth when we leave a UL 744 | depth = depth - 1; 745 | } 746 | return hasActiveChild || isActiveNode; 747 | }; 748 | 749 | // walk the TOC and expand / collapse any items that should be shown 750 | 751 | if (tocEl) { 752 | walk(tocEl, 0); 753 | updateActiveLink(); 754 | } 755 | 756 | // Throttle the scroll event and walk peridiocally 757 | window.document.addEventListener( 758 | "scroll", 759 | throttle(() => { 760 | if (tocEl) { 761 | updateActiveLink(); 762 | walk(tocEl, 0); 763 | } 764 | if (!isReaderMode()) { 765 | hideOverlappedSidebars(); 766 | } 767 | }, 5) 768 | ); 769 | window.addEventListener( 770 | "resize", 771 | throttle(() => { 772 | if (!isReaderMode()) { 773 | hideOverlappedSidebars(); 774 | } 775 | }, 10) 776 | ); 777 | hideOverlappedSidebars(); 778 | highlightReaderToggle(isReaderMode()); 779 | }); 780 | 781 | // grouped tabsets 782 | window.addEventListener("pageshow", (_event) => { 783 | function getTabSettings() { 784 | const data = localStorage.getItem("quarto-persistent-tabsets-data"); 785 | if (!data) { 786 | localStorage.setItem("quarto-persistent-tabsets-data", "{}"); 787 | return {}; 788 | } 789 | if (data) { 790 | return JSON.parse(data); 791 | } 792 | } 793 | 794 | function setTabSettings(data) { 795 | localStorage.setItem( 796 | "quarto-persistent-tabsets-data", 797 | JSON.stringify(data) 798 | ); 799 | } 800 | 801 | function setTabState(groupName, groupValue) { 802 | const data = getTabSettings(); 803 | data[groupName] = groupValue; 804 | setTabSettings(data); 805 | } 806 | 807 | function toggleTab(tab, active) { 808 | const tabPanelId = tab.getAttribute("aria-controls"); 809 | const tabPanel = document.getElementById(tabPanelId); 810 | if (active) { 811 | tab.classList.add("active"); 812 | tabPanel.classList.add("active"); 813 | } else { 814 | tab.classList.remove("active"); 815 | tabPanel.classList.remove("active"); 816 | } 817 | } 818 | 819 | function toggleAll(selectedGroup, selectorsToSync) { 820 | for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) { 821 | const active = selectedGroup === thisGroup; 822 | for (const tab of tabs) { 823 | toggleTab(tab, active); 824 | } 825 | } 826 | } 827 | 828 | function findSelectorsToSyncByLanguage() { 829 | const result = {}; 830 | const tabs = Array.from( 831 | document.querySelectorAll(`div[data-group] a[id^='tabset-']`) 832 | ); 833 | for (const item of tabs) { 834 | const div = item.parentElement.parentElement.parentElement; 835 | const group = div.getAttribute("data-group"); 836 | if (!result[group]) { 837 | result[group] = {}; 838 | } 839 | const selectorsToSync = result[group]; 840 | const value = item.innerHTML; 841 | if (!selectorsToSync[value]) { 842 | selectorsToSync[value] = []; 843 | } 844 | selectorsToSync[value].push(item); 845 | } 846 | return result; 847 | } 848 | 849 | function setupSelectorSync() { 850 | const selectorsToSync = findSelectorsToSyncByLanguage(); 851 | Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => { 852 | Object.entries(tabSetsByValue).forEach(([value, items]) => { 853 | items.forEach((item) => { 854 | item.addEventListener("click", (_event) => { 855 | setTabState(group, value); 856 | toggleAll(value, selectorsToSync[group]); 857 | }); 858 | }); 859 | }); 860 | }); 861 | return selectorsToSync; 862 | } 863 | 864 | const selectorsToSync = setupSelectorSync(); 865 | for (const [group, selectedName] of Object.entries(getTabSettings())) { 866 | const selectors = selectorsToSync[group]; 867 | // it's possible that stale state gives us empty selections, so we explicitly check here. 868 | if (selectors) { 869 | toggleAll(selectedName, selectors); 870 | } 871 | } 872 | }); 873 | 874 | function throttle(func, wait) { 875 | let waiting = false; 876 | return function () { 877 | if (!waiting) { 878 | func.apply(this, arguments); 879 | waiting = true; 880 | setTimeout(function () { 881 | waiting = false; 882 | }, wait); 883 | } 884 | }; 885 | } 886 | 887 | function nexttick(func) { 888 | return setTimeout(func, 0); 889 | } 890 | -------------------------------------------------------------------------------- /dashboard/dashboard_files/libs/quarto-html/tippy.css: -------------------------------------------------------------------------------- 1 | .tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1} -------------------------------------------------------------------------------- /dashboard/dashboard_files/libs/quarto-html/tippy.umd.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],t):(e=e||self).tippy=t(e.Popper)}(this,(function(e){"use strict";var t={passive:!0,capture:!0},n=function(){return document.body};function r(e,t,n){if(Array.isArray(e)){var r=e[t];return null==r?Array.isArray(n)?n[t]:n:r}return e}function o(e,t){var n={}.toString.call(e);return 0===n.indexOf("[object")&&n.indexOf(t+"]")>-1}function i(e,t){return"function"==typeof e?e.apply(void 0,t):e}function a(e,t){return 0===t?e:function(r){clearTimeout(n),n=setTimeout((function(){e(r)}),t)};var n}function s(e,t){var n=Object.assign({},e);return t.forEach((function(e){delete n[e]})),n}function u(e){return[].concat(e)}function c(e,t){-1===e.indexOf(t)&&e.push(t)}function p(e){return e.split("-")[0]}function f(e){return[].slice.call(e)}function l(e){return Object.keys(e).reduce((function(t,n){return void 0!==e[n]&&(t[n]=e[n]),t}),{})}function d(){return document.createElement("div")}function v(e){return["Element","Fragment"].some((function(t){return o(e,t)}))}function m(e){return o(e,"MouseEvent")}function g(e){return!(!e||!e._tippy||e._tippy.reference!==e)}function h(e){return v(e)?[e]:function(e){return o(e,"NodeList")}(e)?f(e):Array.isArray(e)?e:f(document.querySelectorAll(e))}function b(e,t){e.forEach((function(e){e&&(e.style.transitionDuration=t+"ms")}))}function y(e,t){e.forEach((function(e){e&&e.setAttribute("data-state",t)}))}function w(e){var t,n=u(e)[0];return null!=n&&null!=(t=n.ownerDocument)&&t.body?n.ownerDocument:document}function E(e,t,n){var r=t+"EventListener";["transitionend","webkitTransitionEnd"].forEach((function(t){e[r](t,n)}))}function O(e,t){for(var n=t;n;){var r;if(e.contains(n))return!0;n=null==n.getRootNode||null==(r=n.getRootNode())?void 0:r.host}return!1}var x={isTouch:!1},C=0;function T(){x.isTouch||(x.isTouch=!0,window.performance&&document.addEventListener("mousemove",A))}function A(){var e=performance.now();e-C<20&&(x.isTouch=!1,document.removeEventListener("mousemove",A)),C=e}function L(){var e=document.activeElement;if(g(e)){var t=e._tippy;e.blur&&!t.state.isVisible&&e.blur()}}var D=!!("undefined"!=typeof window&&"undefined"!=typeof document)&&!!window.msCrypto,R=Object.assign({appendTo:n,aria:{content:"auto",expanded:"auto"},delay:0,duration:[300,250],getReferenceClientRect:null,hideOnClick:!0,ignoreAttributes:!1,interactive:!1,interactiveBorder:2,interactiveDebounce:0,moveTransition:"",offset:[0,10],onAfterUpdate:function(){},onBeforeUpdate:function(){},onCreate:function(){},onDestroy:function(){},onHidden:function(){},onHide:function(){},onMount:function(){},onShow:function(){},onShown:function(){},onTrigger:function(){},onUntrigger:function(){},onClickOutside:function(){},placement:"top",plugins:[],popperOptions:{},render:null,showOnCreate:!1,touch:!0,trigger:"mouseenter focus",triggerTarget:null},{animateFill:!1,followCursor:!1,inlinePositioning:!1,sticky:!1},{allowHTML:!1,animation:"fade",arrow:!0,content:"",inertia:!1,maxWidth:350,role:"tooltip",theme:"",zIndex:9999}),k=Object.keys(R);function P(e){var t=(e.plugins||[]).reduce((function(t,n){var r,o=n.name,i=n.defaultValue;o&&(t[o]=void 0!==e[o]?e[o]:null!=(r=R[o])?r:i);return t}),{});return Object.assign({},e,t)}function j(e,t){var n=Object.assign({},t,{content:i(t.content,[e])},t.ignoreAttributes?{}:function(e,t){return(t?Object.keys(P(Object.assign({},R,{plugins:t}))):k).reduce((function(t,n){var r=(e.getAttribute("data-tippy-"+n)||"").trim();if(!r)return t;if("content"===n)t[n]=r;else try{t[n]=JSON.parse(r)}catch(e){t[n]=r}return t}),{})}(e,t.plugins));return n.aria=Object.assign({},R.aria,n.aria),n.aria={expanded:"auto"===n.aria.expanded?t.interactive:n.aria.expanded,content:"auto"===n.aria.content?t.interactive?null:"describedby":n.aria.content},n}function M(e,t){e.innerHTML=t}function V(e){var t=d();return!0===e?t.className="tippy-arrow":(t.className="tippy-svg-arrow",v(e)?t.appendChild(e):M(t,e)),t}function I(e,t){v(t.content)?(M(e,""),e.appendChild(t.content)):"function"!=typeof t.content&&(t.allowHTML?M(e,t.content):e.textContent=t.content)}function S(e){var t=e.firstElementChild,n=f(t.children);return{box:t,content:n.find((function(e){return e.classList.contains("tippy-content")})),arrow:n.find((function(e){return e.classList.contains("tippy-arrow")||e.classList.contains("tippy-svg-arrow")})),backdrop:n.find((function(e){return e.classList.contains("tippy-backdrop")}))}}function N(e){var t=d(),n=d();n.className="tippy-box",n.setAttribute("data-state","hidden"),n.setAttribute("tabindex","-1");var r=d();function o(n,r){var o=S(t),i=o.box,a=o.content,s=o.arrow;r.theme?i.setAttribute("data-theme",r.theme):i.removeAttribute("data-theme"),"string"==typeof r.animation?i.setAttribute("data-animation",r.animation):i.removeAttribute("data-animation"),r.inertia?i.setAttribute("data-inertia",""):i.removeAttribute("data-inertia"),i.style.maxWidth="number"==typeof r.maxWidth?r.maxWidth+"px":r.maxWidth,r.role?i.setAttribute("role",r.role):i.removeAttribute("role"),n.content===r.content&&n.allowHTML===r.allowHTML||I(a,e.props),r.arrow?s?n.arrow!==r.arrow&&(i.removeChild(s),i.appendChild(V(r.arrow))):i.appendChild(V(r.arrow)):s&&i.removeChild(s)}return r.className="tippy-content",r.setAttribute("data-state","hidden"),I(r,e.props),t.appendChild(n),n.appendChild(r),o(e.props,e.props),{popper:t,onUpdate:o}}N.$$tippy=!0;var B=1,H=[],U=[];function _(o,s){var v,g,h,C,T,A,L,k,M=j(o,Object.assign({},R,P(l(s)))),V=!1,I=!1,N=!1,_=!1,F=[],W=a(we,M.interactiveDebounce),X=B++,Y=(k=M.plugins).filter((function(e,t){return k.indexOf(e)===t})),$={id:X,reference:o,popper:d(),popperInstance:null,props:M,state:{isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},plugins:Y,clearDelayTimeouts:function(){clearTimeout(v),clearTimeout(g),cancelAnimationFrame(h)},setProps:function(e){if($.state.isDestroyed)return;ae("onBeforeUpdate",[$,e]),be();var t=$.props,n=j(o,Object.assign({},t,l(e),{ignoreAttributes:!0}));$.props=n,he(),t.interactiveDebounce!==n.interactiveDebounce&&(ce(),W=a(we,n.interactiveDebounce));t.triggerTarget&&!n.triggerTarget?u(t.triggerTarget).forEach((function(e){e.removeAttribute("aria-expanded")})):n.triggerTarget&&o.removeAttribute("aria-expanded");ue(),ie(),J&&J(t,n);$.popperInstance&&(Ce(),Ae().forEach((function(e){requestAnimationFrame(e._tippy.popperInstance.forceUpdate)})));ae("onAfterUpdate",[$,e])},setContent:function(e){$.setProps({content:e})},show:function(){var e=$.state.isVisible,t=$.state.isDestroyed,o=!$.state.isEnabled,a=x.isTouch&&!$.props.touch,s=r($.props.duration,0,R.duration);if(e||t||o||a)return;if(te().hasAttribute("disabled"))return;if(ae("onShow",[$],!1),!1===$.props.onShow($))return;$.state.isVisible=!0,ee()&&(z.style.visibility="visible");ie(),de(),$.state.isMounted||(z.style.transition="none");if(ee()){var u=re(),p=u.box,f=u.content;b([p,f],0)}A=function(){var e;if($.state.isVisible&&!_){if(_=!0,z.offsetHeight,z.style.transition=$.props.moveTransition,ee()&&$.props.animation){var t=re(),n=t.box,r=t.content;b([n,r],s),y([n,r],"visible")}se(),ue(),c(U,$),null==(e=$.popperInstance)||e.forceUpdate(),ae("onMount",[$]),$.props.animation&&ee()&&function(e,t){me(e,t)}(s,(function(){$.state.isShown=!0,ae("onShown",[$])}))}},function(){var e,t=$.props.appendTo,r=te();e=$.props.interactive&&t===n||"parent"===t?r.parentNode:i(t,[r]);e.contains(z)||e.appendChild(z);$.state.isMounted=!0,Ce()}()},hide:function(){var e=!$.state.isVisible,t=$.state.isDestroyed,n=!$.state.isEnabled,o=r($.props.duration,1,R.duration);if(e||t||n)return;if(ae("onHide",[$],!1),!1===$.props.onHide($))return;$.state.isVisible=!1,$.state.isShown=!1,_=!1,V=!1,ee()&&(z.style.visibility="hidden");if(ce(),ve(),ie(!0),ee()){var i=re(),a=i.box,s=i.content;$.props.animation&&(b([a,s],o),y([a,s],"hidden"))}se(),ue(),$.props.animation?ee()&&function(e,t){me(e,(function(){!$.state.isVisible&&z.parentNode&&z.parentNode.contains(z)&&t()}))}(o,$.unmount):$.unmount()},hideWithInteractivity:function(e){ne().addEventListener("mousemove",W),c(H,W),W(e)},enable:function(){$.state.isEnabled=!0},disable:function(){$.hide(),$.state.isEnabled=!1},unmount:function(){$.state.isVisible&&$.hide();if(!$.state.isMounted)return;Te(),Ae().forEach((function(e){e._tippy.unmount()})),z.parentNode&&z.parentNode.removeChild(z);U=U.filter((function(e){return e!==$})),$.state.isMounted=!1,ae("onHidden",[$])},destroy:function(){if($.state.isDestroyed)return;$.clearDelayTimeouts(),$.unmount(),be(),delete o._tippy,$.state.isDestroyed=!0,ae("onDestroy",[$])}};if(!M.render)return $;var q=M.render($),z=q.popper,J=q.onUpdate;z.setAttribute("data-tippy-root",""),z.id="tippy-"+$.id,$.popper=z,o._tippy=$,z._tippy=$;var G=Y.map((function(e){return e.fn($)})),K=o.hasAttribute("aria-expanded");return he(),ue(),ie(),ae("onCreate",[$]),M.showOnCreate&&Le(),z.addEventListener("mouseenter",(function(){$.props.interactive&&$.state.isVisible&&$.clearDelayTimeouts()})),z.addEventListener("mouseleave",(function(){$.props.interactive&&$.props.trigger.indexOf("mouseenter")>=0&&ne().addEventListener("mousemove",W)})),$;function Q(){var e=$.props.touch;return Array.isArray(e)?e:[e,0]}function Z(){return"hold"===Q()[0]}function ee(){var e;return!(null==(e=$.props.render)||!e.$$tippy)}function te(){return L||o}function ne(){var e=te().parentNode;return e?w(e):document}function re(){return S(z)}function oe(e){return $.state.isMounted&&!$.state.isVisible||x.isTouch||C&&"focus"===C.type?0:r($.props.delay,e?0:1,R.delay)}function ie(e){void 0===e&&(e=!1),z.style.pointerEvents=$.props.interactive&&!e?"":"none",z.style.zIndex=""+$.props.zIndex}function ae(e,t,n){var r;(void 0===n&&(n=!0),G.forEach((function(n){n[e]&&n[e].apply(n,t)})),n)&&(r=$.props)[e].apply(r,t)}function se(){var e=$.props.aria;if(e.content){var t="aria-"+e.content,n=z.id;u($.props.triggerTarget||o).forEach((function(e){var r=e.getAttribute(t);if($.state.isVisible)e.setAttribute(t,r?r+" "+n:n);else{var o=r&&r.replace(n,"").trim();o?e.setAttribute(t,o):e.removeAttribute(t)}}))}}function ue(){!K&&$.props.aria.expanded&&u($.props.triggerTarget||o).forEach((function(e){$.props.interactive?e.setAttribute("aria-expanded",$.state.isVisible&&e===te()?"true":"false"):e.removeAttribute("aria-expanded")}))}function ce(){ne().removeEventListener("mousemove",W),H=H.filter((function(e){return e!==W}))}function pe(e){if(!x.isTouch||!N&&"mousedown"!==e.type){var t=e.composedPath&&e.composedPath()[0]||e.target;if(!$.props.interactive||!O(z,t)){if(u($.props.triggerTarget||o).some((function(e){return O(e,t)}))){if(x.isTouch)return;if($.state.isVisible&&$.props.trigger.indexOf("click")>=0)return}else ae("onClickOutside",[$,e]);!0===$.props.hideOnClick&&($.clearDelayTimeouts(),$.hide(),I=!0,setTimeout((function(){I=!1})),$.state.isMounted||ve())}}}function fe(){N=!0}function le(){N=!1}function de(){var e=ne();e.addEventListener("mousedown",pe,!0),e.addEventListener("touchend",pe,t),e.addEventListener("touchstart",le,t),e.addEventListener("touchmove",fe,t)}function ve(){var e=ne();e.removeEventListener("mousedown",pe,!0),e.removeEventListener("touchend",pe,t),e.removeEventListener("touchstart",le,t),e.removeEventListener("touchmove",fe,t)}function me(e,t){var n=re().box;function r(e){e.target===n&&(E(n,"remove",r),t())}if(0===e)return t();E(n,"remove",T),E(n,"add",r),T=r}function ge(e,t,n){void 0===n&&(n=!1),u($.props.triggerTarget||o).forEach((function(r){r.addEventListener(e,t,n),F.push({node:r,eventType:e,handler:t,options:n})}))}function he(){var e;Z()&&(ge("touchstart",ye,{passive:!0}),ge("touchend",Ee,{passive:!0})),(e=$.props.trigger,e.split(/\s+/).filter(Boolean)).forEach((function(e){if("manual"!==e)switch(ge(e,ye),e){case"mouseenter":ge("mouseleave",Ee);break;case"focus":ge(D?"focusout":"blur",Oe);break;case"focusin":ge("focusout",Oe)}}))}function be(){F.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),F=[]}function ye(e){var t,n=!1;if($.state.isEnabled&&!xe(e)&&!I){var r="focus"===(null==(t=C)?void 0:t.type);C=e,L=e.currentTarget,ue(),!$.state.isVisible&&m(e)&&H.forEach((function(t){return t(e)})),"click"===e.type&&($.props.trigger.indexOf("mouseenter")<0||V)&&!1!==$.props.hideOnClick&&$.state.isVisible?n=!0:Le(e),"click"===e.type&&(V=!n),n&&!r&&De(e)}}function we(e){var t=e.target,n=te().contains(t)||z.contains(t);"mousemove"===e.type&&n||function(e,t){var n=t.clientX,r=t.clientY;return e.every((function(e){var t=e.popperRect,o=e.popperState,i=e.props.interactiveBorder,a=p(o.placement),s=o.modifiersData.offset;if(!s)return!0;var u="bottom"===a?s.top.y:0,c="top"===a?s.bottom.y:0,f="right"===a?s.left.x:0,l="left"===a?s.right.x:0,d=t.top-r+u>i,v=r-t.bottom-c>i,m=t.left-n+f>i,g=n-t.right-l>i;return d||v||m||g}))}(Ae().concat(z).map((function(e){var t,n=null==(t=e._tippy.popperInstance)?void 0:t.state;return n?{popperRect:e.getBoundingClientRect(),popperState:n,props:M}:null})).filter(Boolean),e)&&(ce(),De(e))}function Ee(e){xe(e)||$.props.trigger.indexOf("click")>=0&&V||($.props.interactive?$.hideWithInteractivity(e):De(e))}function Oe(e){$.props.trigger.indexOf("focusin")<0&&e.target!==te()||$.props.interactive&&e.relatedTarget&&z.contains(e.relatedTarget)||De(e)}function xe(e){return!!x.isTouch&&Z()!==e.type.indexOf("touch")>=0}function Ce(){Te();var t=$.props,n=t.popperOptions,r=t.placement,i=t.offset,a=t.getReferenceClientRect,s=t.moveTransition,u=ee()?S(z).arrow:null,c=a?{getBoundingClientRect:a,contextElement:a.contextElement||te()}:o,p=[{name:"offset",options:{offset:i}},{name:"preventOverflow",options:{padding:{top:2,bottom:2,left:5,right:5}}},{name:"flip",options:{padding:5}},{name:"computeStyles",options:{adaptive:!s}},{name:"$$tippy",enabled:!0,phase:"beforeWrite",requires:["computeStyles"],fn:function(e){var t=e.state;if(ee()){var n=re().box;["placement","reference-hidden","escaped"].forEach((function(e){"placement"===e?n.setAttribute("data-placement",t.placement):t.attributes.popper["data-popper-"+e]?n.setAttribute("data-"+e,""):n.removeAttribute("data-"+e)})),t.attributes.popper={}}}}];ee()&&u&&p.push({name:"arrow",options:{element:u,padding:3}}),p.push.apply(p,(null==n?void 0:n.modifiers)||[]),$.popperInstance=e.createPopper(c,z,Object.assign({},n,{placement:r,onFirstUpdate:A,modifiers:p}))}function Te(){$.popperInstance&&($.popperInstance.destroy(),$.popperInstance=null)}function Ae(){return f(z.querySelectorAll("[data-tippy-root]"))}function Le(e){$.clearDelayTimeouts(),e&&ae("onTrigger",[$,e]),de();var t=oe(!0),n=Q(),r=n[0],o=n[1];x.isTouch&&"hold"===r&&o&&(t=o),t?v=setTimeout((function(){$.show()}),t):$.show()}function De(e){if($.clearDelayTimeouts(),ae("onUntrigger",[$,e]),$.state.isVisible){if(!($.props.trigger.indexOf("mouseenter")>=0&&$.props.trigger.indexOf("click")>=0&&["mouseleave","mousemove"].indexOf(e.type)>=0&&V)){var t=oe(!1);t?g=setTimeout((function(){$.state.isVisible&&$.hide()}),t):h=requestAnimationFrame((function(){$.hide()}))}}else ve()}}function F(e,n){void 0===n&&(n={});var r=R.plugins.concat(n.plugins||[]);document.addEventListener("touchstart",T,t),window.addEventListener("blur",L);var o=Object.assign({},n,{plugins:r}),i=h(e).reduce((function(e,t){var n=t&&_(t,o);return n&&e.push(n),e}),[]);return v(e)?i[0]:i}F.defaultProps=R,F.setDefaultProps=function(e){Object.keys(e).forEach((function(t){R[t]=e[t]}))},F.currentInput=x;var W=Object.assign({},e.applyStyles,{effect:function(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow)}}),X={mouseover:"mouseenter",focusin:"focus",click:"click"};var Y={name:"animateFill",defaultValue:!1,fn:function(e){var t;if(null==(t=e.props.render)||!t.$$tippy)return{};var n=S(e.popper),r=n.box,o=n.content,i=e.props.animateFill?function(){var e=d();return e.className="tippy-backdrop",y([e],"hidden"),e}():null;return{onCreate:function(){i&&(r.insertBefore(i,r.firstElementChild),r.setAttribute("data-animatefill",""),r.style.overflow="hidden",e.setProps({arrow:!1,animation:"shift-away"}))},onMount:function(){if(i){var e=r.style.transitionDuration,t=Number(e.replace("ms",""));o.style.transitionDelay=Math.round(t/10)+"ms",i.style.transitionDuration=e,y([i],"visible")}},onShow:function(){i&&(i.style.transitionDuration="0ms")},onHide:function(){i&&y([i],"hidden")}}}};var $={clientX:0,clientY:0},q=[];function z(e){var t=e.clientX,n=e.clientY;$={clientX:t,clientY:n}}var J={name:"followCursor",defaultValue:!1,fn:function(e){var t=e.reference,n=w(e.props.triggerTarget||t),r=!1,o=!1,i=!0,a=e.props;function s(){return"initial"===e.props.followCursor&&e.state.isVisible}function u(){n.addEventListener("mousemove",f)}function c(){n.removeEventListener("mousemove",f)}function p(){r=!0,e.setProps({getReferenceClientRect:null}),r=!1}function f(n){var r=!n.target||t.contains(n.target),o=e.props.followCursor,i=n.clientX,a=n.clientY,s=t.getBoundingClientRect(),u=i-s.left,c=a-s.top;!r&&e.props.interactive||e.setProps({getReferenceClientRect:function(){var e=t.getBoundingClientRect(),n=i,r=a;"initial"===o&&(n=e.left+u,r=e.top+c);var s="horizontal"===o?e.top:r,p="vertical"===o?e.right:n,f="horizontal"===o?e.bottom:r,l="vertical"===o?e.left:n;return{width:p-l,height:f-s,top:s,right:p,bottom:f,left:l}}})}function l(){e.props.followCursor&&(q.push({instance:e,doc:n}),function(e){e.addEventListener("mousemove",z)}(n))}function d(){0===(q=q.filter((function(t){return t.instance!==e}))).filter((function(e){return e.doc===n})).length&&function(e){e.removeEventListener("mousemove",z)}(n)}return{onCreate:l,onDestroy:d,onBeforeUpdate:function(){a=e.props},onAfterUpdate:function(t,n){var i=n.followCursor;r||void 0!==i&&a.followCursor!==i&&(d(),i?(l(),!e.state.isMounted||o||s()||u()):(c(),p()))},onMount:function(){e.props.followCursor&&!o&&(i&&(f($),i=!1),s()||u())},onTrigger:function(e,t){m(t)&&($={clientX:t.clientX,clientY:t.clientY}),o="focus"===t.type},onHidden:function(){e.props.followCursor&&(p(),c(),i=!0)}}}};var G={name:"inlinePositioning",defaultValue:!1,fn:function(e){var t,n=e.reference;var r=-1,o=!1,i=[],a={name:"tippyInlinePositioning",enabled:!0,phase:"afterWrite",fn:function(o){var a=o.state;e.props.inlinePositioning&&(-1!==i.indexOf(a.placement)&&(i=[]),t!==a.placement&&-1===i.indexOf(a.placement)&&(i.push(a.placement),e.setProps({getReferenceClientRect:function(){return function(e){return function(e,t,n,r){if(n.length<2||null===e)return t;if(2===n.length&&r>=0&&n[0].left>n[1].right)return n[r]||t;switch(e){case"top":case"bottom":var o=n[0],i=n[n.length-1],a="top"===e,s=o.top,u=i.bottom,c=a?o.left:i.left,p=a?o.right:i.right;return{top:s,bottom:u,left:c,right:p,width:p-c,height:u-s};case"left":case"right":var f=Math.min.apply(Math,n.map((function(e){return e.left}))),l=Math.max.apply(Math,n.map((function(e){return e.right}))),d=n.filter((function(t){return"left"===e?t.left===f:t.right===l})),v=d[0].top,m=d[d.length-1].bottom;return{top:v,bottom:m,left:f,right:l,width:l-f,height:m-v};default:return t}}(p(e),n.getBoundingClientRect(),f(n.getClientRects()),r)}(a.placement)}})),t=a.placement)}};function s(){var t;o||(t=function(e,t){var n;return{popperOptions:Object.assign({},e.popperOptions,{modifiers:[].concat(((null==(n=e.popperOptions)?void 0:n.modifiers)||[]).filter((function(e){return e.name!==t.name})),[t])})}}(e.props,a),o=!0,e.setProps(t),o=!1)}return{onCreate:s,onAfterUpdate:s,onTrigger:function(t,n){if(m(n)){var o=f(e.reference.getClientRects()),i=o.find((function(e){return e.left-2<=n.clientX&&e.right+2>=n.clientX&&e.top-2<=n.clientY&&e.bottom+2>=n.clientY})),a=o.indexOf(i);r=a>-1?a:r}},onHidden:function(){r=-1}}}};var K={name:"sticky",defaultValue:!1,fn:function(e){var t=e.reference,n=e.popper;function r(t){return!0===e.props.sticky||e.props.sticky===t}var o=null,i=null;function a(){var s=r("reference")?(e.popperInstance?e.popperInstance.state.elements.reference:t).getBoundingClientRect():null,u=r("popper")?n.getBoundingClientRect():null;(s&&Q(o,s)||u&&Q(i,u))&&e.popperInstance&&e.popperInstance.update(),o=s,i=u,e.state.isMounted&&requestAnimationFrame(a)}return{onMount:function(){e.props.sticky&&a()}}}};function Q(e,t){return!e||!t||(e.top!==t.top||e.right!==t.right||e.bottom!==t.bottom||e.left!==t.left)}return F.setDefaultProps({plugins:[Y,J,G,K],render:N}),F.createSingleton=function(e,t){var n;void 0===t&&(t={});var r,o=e,i=[],a=[],c=t.overrides,p=[],f=!1;function l(){a=o.map((function(e){return u(e.props.triggerTarget||e.reference)})).reduce((function(e,t){return e.concat(t)}),[])}function v(){i=o.map((function(e){return e.reference}))}function m(e){o.forEach((function(t){e?t.enable():t.disable()}))}function g(e){return o.map((function(t){var n=t.setProps;return t.setProps=function(o){n(o),t.reference===r&&e.setProps(o)},function(){t.setProps=n}}))}function h(e,t){var n=a.indexOf(t);if(t!==r){r=t;var s=(c||[]).concat("content").reduce((function(e,t){return e[t]=o[n].props[t],e}),{});e.setProps(Object.assign({},s,{getReferenceClientRect:"function"==typeof s.getReferenceClientRect?s.getReferenceClientRect:function(){var e;return null==(e=i[n])?void 0:e.getBoundingClientRect()}}))}}m(!1),v(),l();var b={fn:function(){return{onDestroy:function(){m(!0)},onHidden:function(){r=null},onClickOutside:function(e){e.props.showOnCreate&&!f&&(f=!0,r=null)},onShow:function(e){e.props.showOnCreate&&!f&&(f=!0,h(e,i[0]))},onTrigger:function(e,t){h(e,t.currentTarget)}}}},y=F(d(),Object.assign({},s(t,["overrides"]),{plugins:[b].concat(t.plugins||[]),triggerTarget:a,popperOptions:Object.assign({},t.popperOptions,{modifiers:[].concat((null==(n=t.popperOptions)?void 0:n.modifiers)||[],[W])})})),w=y.show;y.show=function(e){if(w(),!r&&null==e)return h(y,i[0]);if(!r||null!=e){if("number"==typeof e)return i[e]&&h(y,i[e]);if(o.indexOf(e)>=0){var t=e.reference;return h(y,t)}return i.indexOf(e)>=0?h(y,e):void 0}},y.showNext=function(){var e=i[0];if(!r)return y.show(0);var t=i.indexOf(r);y.show(i[t+1]||e)},y.showPrevious=function(){var e=i[i.length-1];if(!r)return y.show(e);var t=i.indexOf(r),n=i[t-1]||e;y.show(n)};var E=y.setProps;return y.setProps=function(e){c=e.overrides||c,E(e)},y.setInstances=function(e){m(!0),p.forEach((function(e){return e()})),o=e,m(!1),v(),l(),p=g(y),y.setProps({triggerTarget:a})},p=g(y),y},F.delegate=function(e,n){var r=[],o=[],i=!1,a=n.target,c=s(n,["target"]),p=Object.assign({},c,{trigger:"manual",touch:!1}),f=Object.assign({touch:R.touch},c,{showOnCreate:!0}),l=F(e,p);function d(e){if(e.target&&!i){var t=e.target.closest(a);if(t){var r=t.getAttribute("data-tippy-trigger")||n.trigger||R.trigger;if(!t._tippy&&!("touchstart"===e.type&&"boolean"==typeof f.touch||"touchstart"!==e.type&&r.indexOf(X[e.type])<0)){var s=F(t,f);s&&(o=o.concat(s))}}}}function v(e,t,n,o){void 0===o&&(o=!1),e.addEventListener(t,n,o),r.push({node:e,eventType:t,handler:n,options:o})}return u(l).forEach((function(e){var n=e.destroy,a=e.enable,s=e.disable;e.destroy=function(e){void 0===e&&(e=!0),e&&o.forEach((function(e){e.destroy()})),o=[],r.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),r=[],n()},e.enable=function(){a(),o.forEach((function(e){return e.enable()})),i=!1},e.disable=function(){s(),o.forEach((function(e){return e.disable()})),i=!0},function(e){var n=e.reference;v(n,"touchstart",d,t),v(n,"mouseover",d),v(n,"focusin",d),v(n,"click",d)}(e)})),l},F.hideAll=function(e){var t=void 0===e?{}:e,n=t.exclude,r=t.duration;U.forEach((function(e){var t=!1;if(n&&(t=g(n)?e.reference===n:e.popper===n.popper),!t){var o=e.props.duration;e.setProps({duration:r}),e.hide(),e.state.isDestroyed||e.setProps({duration:o})}}))},F.roundArrow='',F})); 2 | 3 | -------------------------------------------------------------------------------- /dashboard/dashboard_files/libs/quarto-ojs/quarto-ojs.css: -------------------------------------------------------------------------------- 1 | span.ojs-inline span div { 2 | display: inline-block; 3 | } 4 | 5 | /* add some breathing room between display outputs and text especially */ 6 | div.cell + section, 7 | div.cell + h1, 8 | div.cell + h2, 9 | div.cell + h3, 10 | div.cell + h4, 11 | div.cell + h5, 12 | div.cell + h6, 13 | div.cell + p { 14 | margin-top: 1rem; 15 | } 16 | 17 | div[nodetype="declaration"]:not([data-output="all"]) 18 | div.observablehq:not(observablehq--error) { 19 | display: none; 20 | } 21 | 22 | /* mimic bootstrap behavior which is always correct in observablehq outputs */ 23 | .observablehq a:not([href]):not([class]), 24 | .observablehq a:not([href]):not([class]):hover { 25 | color: inherit; 26 | text-decoration: none; 27 | } 28 | 29 | .observablehq .observablehq--inspect { 30 | --code-fallback: Consolas, monaco, monospace; 31 | font-family: var(--bs-font-monospace, var(--code-fallback)); 32 | font-size: 0.8em; 33 | } 34 | 35 | .observablehq--field { 36 | margin-left: 1rem; 37 | } 38 | 39 | .observablehq--caret { 40 | margin-right: 2px; 41 | vertical-align: baseline; 42 | } 43 | 44 | .observablehq--collapsed, 45 | .observablehq--expanded.observablehq--inspect a { 46 | cursor: pointer; 47 | } 48 | 49 | /* classes directly from observable's runtime */ 50 | .observablehq--key, 51 | .observablehq--index { 52 | color: var(--quarto-hl-dt-color); 53 | } 54 | 55 | .observablehq--string { 56 | color: var(--quarto-hl-st-color); 57 | } 58 | 59 | .observablehq--bigint, 60 | .observablehq--date, 61 | .observablehq--number, 62 | .observablehq--regexp, 63 | .observablehq--symbol { 64 | color: var(--quarto-hl-dv-color); 65 | } 66 | 67 | .observablehq--null, 68 | .observablehq--boolean, 69 | .observablehq--undefined, 70 | .observablehq--keyword { 71 | color: var(--quarto-hl-kw-color); 72 | } 73 | 74 | /* In addition, their import statements specifically come highlighted by hljs. 75 | (probably some legacy feature of theirs?) We handle those here as well. 76 | 77 | Just to be on the safe side, we select on observable's 'md-pre' 78 | class as well, in case someone else uses hljs and wishes to put 79 | their own highlighting. 80 | 81 | TODO Note that to make our highlighting consistent, we're 82 | overriding the "im" class to present like a keyword. I should make 83 | sure this looks right everywhere, but I don't know how to test it 84 | comprehensively. 85 | */ 86 | 87 | code.javascript span.im { 88 | color: var(--quarto-hl-kw-color); 89 | } 90 | 91 | pre.observablehq--md-pre span.hljs-keyword { 92 | color: var(--quarto-hl-kw-color); 93 | } 94 | 95 | pre.observablehq--md-pre span.hljs-string { 96 | color: var(--quarto-hl-st-color); 97 | } 98 | 99 | pre.observablehq--md-pre .span.hljs-date, 100 | pre.observablehq--md-pre .span.hljs-number, 101 | pre.observablehq--md-pre .span.hljs-regexp, 102 | pre.observablehq--md-pre .span.hljs-symbol { 103 | color: var(--quarto-hl-dv-color); 104 | } 105 | 106 | /* Other general niceties, but it's possible that we should do this on a page-by-page basis */ 107 | 108 | input { 109 | vertical-align: middle; 110 | } 111 | 112 | input[type="radio"], 113 | input[type="checkbox"] { 114 | margin: 0px 0px 3px 0px; 115 | } 116 | 117 | .observable-in-a-box-waiting-for-module-import { 118 | visibility: hidden; 119 | } 120 | 121 | /* play nicely w/ sidebar layout */ 122 | .panel-sidebar .observablehq > form[class^="oi-"] { 123 | flex-wrap: wrap !important; 124 | } 125 | 126 | /* likely that this only makes sense with bootstrap. TODO check with charles */ 127 | .observablehq table { 128 | font-size: 0.9em !important; 129 | } 130 | 131 | .quarto-ojs-hide { 132 | display: none; 133 | } 134 | 135 | .quarto-ojs-error-pinpoint { 136 | border-bottom: 2px dotted #e51400; 137 | font-weight: 700; 138 | cursor: pointer; 139 | } 140 | 141 | code span.quarto-ojs-error-pinpoint { 142 | color: inherit; 143 | } 144 | 145 | .observablehq--error .observablehq--inspect { 146 | font-size: 0.875em !important; 147 | } 148 | 149 | .observablehq--error .callout { 150 | margin-bottom: 0; 151 | margin-top: 0; 152 | } 153 | 154 | /* this counteracts the negative margin which observablehq uses in their tables at runtime */ 155 | .quarto-ojs-table-fixup { 156 | padding-left: 14px; 157 | } 158 | 159 | div.observablehq input[type="date"] { 160 | border-style: solid; 161 | padding: 0.8em 0.5em 0.8em 0.5em; 162 | border-color: rgba(220, 220, 220, 0.3); 163 | } 164 | -------------------------------------------------------------------------------- /dashboard/data/tswift.csv: -------------------------------------------------------------------------------- 1 | album_name,ep,album_release,track_number,track_name,artist,featuring,bonus_track,promotional_release,single_release,track_release,danceability,energy,key,loudness,mode,speechiness,acousticness,instrumentalness,liveness,valence,tempo,time_signature,duration_ms,explicit,key_name,mode_name,key_mode,lyrics 2 | Fearless (Taylor's Version),FALSE,4/9/21,26,Bye Bye Baby (Taylor's Version) [From The Vault],Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.624,0.624,5,-7.86,1,0.053900000000000003,0.334,0,0.0995,0.527,80.132,4,242158,FALSE,F,major,F major, 3 | Lover,FALSE,8/23/19,1,I Forgot That You Existed,Taylor Swift,NA,FALSE,NA,NA,8/23/19,0.664,0.316,5,-10.345,1,0.519,0.298,2.03e-6,0.0812,0.541,92.875,4,170640,FALSE,F,major,F major, 4 | Midnights,FALSE,10/21/22,8,Vigilante Shit,Taylor Swift,NA,FALSE,NA,NA,10/21/22,0.87,0.279,4,-11.134,0,0.364,0.16,1.74e-5,0.121,0.16,79.996,4,164801,TRUE,E,minor,E minor, 5 | evermore,FALSE,12/11/20,14,closure,Taylor Swift,NA,FALSE,NA,NA,12/11/20,0.689,0.704,9,-10.813,1,0.245,0.835,4.83e-6,0.134,0.92,151.884,5,180653,FALSE,A,major,A major, 6 | Lover,FALSE,8/23/19,13,False God,Taylor Swift,NA,FALSE,NA,NA,8/23/19,0.739,0.32,11,-10.862,0,0.239,0.736,1.47e-4,0.111,0.351,79.97,4,200307,FALSE,B,minor,B minor, 7 | Midnights,FALSE,10/21/22,18,Glitch,Taylor Swift,NA,TRUE,NA,NA,10/21/22,0.675,0.466,11,-9.738,0,0.201,0.361,0,0.11,0.347,140.864,4,148781,FALSE,B,minor,B minor, 8 | reputation,FALSE,11/10/17,11,Dancing With Our Hands Tied,Taylor Swift,NA,FALSE,NA,NA,11/10/17,0.624,0.691,0,-6.686,0,0.196,0.0604,1.1e-5,0.138,0.284,160.024,4,211507,FALSE,C,minor,C minor, 9 | 1989,FALSE,10/27/14,8,Bad Blood,Taylor Swift,NA,FALSE,NA,NA,10/27/14,0.646,0.794,7,-6.104,1,0.19,0.0885,6.16e-6,0.201,0.287,170.216,4,211933,FALSE,G,major,G major, 10 | Red (Taylor's Version),FALSE,11/12/21,8,We Are Never Ever Getting Back Together (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.567,0.686,7,-6.139,1,0.175,0.0317,1.86e-6,0.0732,0.716,172.014,4,193147,FALSE,G,major,G major, 11 | evermore,FALSE,12/11/20,1,willow,Taylor Swift,NA,FALSE,NA,12/11/20,12/11/20,0.392,0.574,7,-9.195,1,0.17,0.833,0.00179,0.145,0.529,81.112,4,214707,FALSE,G,major,G major, 12 | 1989,FALSE,10/27/14,6,Shake It Off,Taylor Swift,NA,FALSE,NA,8/18/14,8/18/14,0.647,0.8,7,-5.384,1,0.165,0.0647,0,0.334,0.942,160.078,4,219200,FALSE,G,major,G major, 13 | Midnights,FALSE,10/21/22,7,Question...?,Taylor Swift,NA,FALSE,NA,NA,10/21/22,0.751,0.502,7,-8.777,1,0.162,0.226,0,0.297,0.112,108.989,4,210557,TRUE,G,major,G major, 14 | reputation,FALSE,11/10/17,3,I Did Something Bad,Taylor Swift,NA,FALSE,NA,NA,11/10/17,0.696,0.602,0,-6.156,0,0.159,0.0679,2.11e-5,0.0696,0.305,82.989,4,238253,FALSE,C,minor,C minor, 15 | Lover,FALSE,8/23/19,2,Cruel Summer,Taylor Swift,NA,FALSE,NA,NA,8/23/19,0.552,0.702,9,-5.707,1,0.157,0.117,2.06e-5,0.105,0.564,169.994,4,178427,FALSE,A,major,A major, 16 | reputation,FALSE,11/10/17,1,...Ready For It?,Taylor Swift,NA,FALSE,NA,10/24/17,10/24/17,0.613,0.764,2,-6.509,1,0.136,0.0527,0,0.197,0.417,160.015,4,208187,FALSE,D,major,D major, 17 | reputation,FALSE,11/10/17,8,Gorgeous,Taylor Swift,NA,FALSE,10/20/17,NA,10/20/17,0.8,0.535,7,-6.684,1,0.135,0.0713,9.48e-6,0.213,0.451,92.027,4,209680,FALSE,G,major,G major, 18 | reputation,FALSE,11/10/17,9,Getaway Car,Taylor Swift,NA,FALSE,NA,9/27/18,11/10/17,0.562,0.689,2,-6.745,1,0.127,0.00465,2.23e-6,0.0888,0.351,172.054,4,233627,FALSE,D,major,D major, 19 | reputation,FALSE,11/10/17,6,Look What You Made Me Do,Taylor Swift,NA,FALSE,NA,8/24/17,8/24/17,0.766,0.709,9,-6.471,0,0.123,0.204,1.41e-5,0.126,0.506,128.07,4,211853,FALSE,A,minor,A minor, 20 | Midnights,FALSE,10/21/22,19,"Would've, Could've, Should've",Taylor Swift,NA,TRUE,NA,NA,10/21/22,0.483,0.84,7,-6.508,1,0.119,0.43,5.75e-4,0.146,0.55,157.979,4,260362,FALSE,G,major,G major, 21 | Midnights,FALSE,10/21/22,13,Mastermind,Taylor Swift,NA,FALSE,NA,NA,10/21/22,0.661,0.352,4,-14.077,1,0.117,0.571,0.00147,0.0884,0.13,125.934,4,191039,FALSE,E,major,E major, 22 | reputation,FALSE,11/10/17,13,This Is Why We Can't Have Nice Things,Taylor Swift,NA,FALSE,NA,NA,11/10/17,0.567,0.789,5,-5.986,1,0.115,0.0156,0,0.382,0.438,163.96,4,207133,FALSE,F,major,F major, 23 | Red (Taylor's Version),FALSE,11/12/21,24,Babe (Taylor's Version) [From The Vault],Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.584,0.743,4,-7.075,1,0.0931,0.0538,2.83e-6,0.121,0.746,167.844,4,224240,FALSE,E,major,E major, 24 | Lover,FALSE,8/23/19,3,Lover,Taylor Swift,NA,FALSE,NA,8/16/19,8/16/19,0.359,0.543,7,-7.582,1,0.0919,0.492,1.58e-5,0.118,0.453,68.534,4,221307,FALSE,G,major,G major, 25 | Red,FALSE,10/22/12,8,We Are Never Ever Getting Back Together,Taylor Swift,NA,FALSE,NA,8/13/12,8/13/12,0.628,0.676,7,-5.911,1,0.0916,0.00957,2.65e-5,0.102,0.75,85.984,4,191880,FALSE,G,major,G major, 26 | 1989,FALSE,10/27/14,9,Wildest Dreams,Taylor Swift,NA,FALSE,NA,8/31/15,10/27/14,0.55,0.688,8,-7.416,1,0.0897,0.0692,0.00144,0.106,0.465,139.997,4,220440,FALSE,G#,major,G# major, 27 | Midnights,FALSE,10/21/22,17,High Infidelity,Taylor Swift,NA,TRUE,NA,NA,10/21/22,0.646,0.529,10,-10.179,1,0.0864,0.723,0,0.0885,0.755,87.862,4,231475,FALSE,A#,major,A# major, 28 | Speak Now,FALSE,10/25/10,10,Better Than Revenge,Taylor Swift,NA,FALSE,NA,NA,10/25/10,0.516,0.917,11,-3.186,0,0.0827,0.0139,6.37e-6,0.36,0.635,145.821,4,217160,FALSE,B,minor,B minor, 29 | Lover,FALSE,8/23/19,9,Cornelia Street,Taylor Swift,NA,FALSE,NA,NA,8/23/19,0.824,0.624,0,-9.728,1,0.0827,0.781,1.89e-4,0.1,0.248,102.012,4,287267,FALSE,C,major,C major, 30 | folklore,FALSE,7/24/20,15,peace,Taylor Swift,NA,FALSE,NA,NA,7/24/20,0.644,0.284,5,-12.879,1,0.0821,0.916,1.5e-5,0.0909,0.328,150.072,4,234000,TRUE,F,major,F major, 31 | Midnights,FALSE,10/21/22,16,Paris,Taylor Swift,NA,TRUE,NA,NA,10/21/22,0.7,0.509,7,-10.547,1,0.0789,0.112,3.27e-5,0.137,0.345,110.947,4,196259,FALSE,G,major,G major, 32 | Red,FALSE,10/22/12,11,Holy Ground,Taylor Swift,NA,FALSE,NA,NA,10/22/12,0.627,0.816,4,-6.698,1,0.0774,0.0162,0.00208,0.0966,0.648,157.043,4,201853,FALSE,E,major,E major, 33 | Lover,FALSE,8/23/19,7,Miss Americana & The Heartbreak Prince,Taylor Swift,NA,FALSE,NA,NA,8/23/19,0.662,0.747,11,-6.926,0,0.0736,0.028,0.00615,0.138,0.487,150.088,4,234147,FALSE,B,minor,B minor, 34 | reputation,FALSE,11/10/17,7,So It Goes...,Taylor Swift,NA,FALSE,NA,NA,11/10/17,0.574,0.61,2,-7.283,1,0.0732,0.122,1.1e-6,0.13,0.374,74.957,4,227907,FALSE,D,major,D major, 35 | reputation,FALSE,11/10/17,14,Call It What You Want,Taylor Swift,NA,FALSE,11/3/17,NA,11/3/17,0.598,0.504,9,-9.874,1,0.0731,0.186,2.21e-4,0.34,0.252,163.954,4,203507,FALSE,A,major,A major, 36 | 1989,FALSE,10/27/14,16,New Romantics,Taylor Swift,NA,TRUE,3/3/15,2/23/16,10/27/14,0.633,0.889,5,-5.87,1,0.0715,0.00463,4.58e-4,0.0658,0.584,121.956,4,230467,FALSE,F,major,F major, 37 | 1989,FALSE,10/27/14,12,I Know Places,Taylor Swift,NA,FALSE,NA,NA,10/27/14,0.602,0.755,0,-4.991,1,0.071,0.245,0,0.178,0.495,159.965,4,195707,FALSE,C,major,C major, 38 | Midnights,FALSE,10/21/22,6,Midnight Rain,Taylor Swift,NA,FALSE,NA,NA,10/21/22,0.636,0.377,0,-11.721,1,0.0708,0.71,6.51e-5,0.115,0.23,139.966,4,174783,FALSE,C,major,C major, 39 | Midnights,FALSE,10/21/22,20,Dear Reader,Taylor Swift,NA,TRUE,NA,NA,10/21/22,0.562,0.388,0,-12.088,1,0.0705,0.481,0.00145,0.117,0.159,107.747,4,225194,FALSE,C,major,C major, 40 | Midnights,FALSE,10/21/22,1,Lavender Haze,Taylor Swift,NA,FALSE,NA,11/29/22,10/21/22,0.735,0.444,10,-10.519,1,0.0684,0.204,0.0012,0.17,0.0984,97.038,4,202396,TRUE,A#,major,A# major, 41 | reputation,FALSE,11/10/17,5,Delicate,Taylor Swift,NA,FALSE,NA,3/12/18,11/10/17,0.75,0.404,9,-10.178,0,0.0682,0.216,3.57e-4,0.0911,0.0499,95.045,4,232253,FALSE,A,minor,A minor, 42 | Midnights,FALSE,10/21/22,9,Bejeweled,Taylor Swift,NA,FALSE,10/25/22,NA,10/21/22,0.7,0.55,7,-9.13,1,0.0653,0.0661,1.02e-4,0.091,0.412,164.003,4,194166,FALSE,G,major,G major, 43 | Taylor Swift,FALSE,10/24/06,12,I'm Only Me When I'm With You,Taylor Swift,NA,TRUE,NA,NA,3/18/08,0.563,0.934,8,-3.629,1,0.0646,0.00452,8.07e-4,0.103,0.518,143.964,4,213053,FALSE,G#,major,G# major, 44 | Lover,FALSE,8/23/19,12,Soon You'll Get Better,Taylor Swift,The Chicks,FALSE,NA,NA,8/23/19,0.433,0.182,0,-12.566,1,0.0641,0.907,0,0.123,0.421,207.476,4,201587,FALSE,C,major,C major, 45 | Red (Taylor's Version),FALSE,11/12/21,11,Holy Ground (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.622,0.809,9,-5.623,1,0.0638,0.0288,0.00218,0.109,0.511,156.894,4,202960,FALSE,A,major,A major, 46 | Lover,FALSE,8/23/19,10,Death By A Thousand Cuts,Taylor Swift,NA,FALSE,NA,NA,8/23/19,0.712,0.732,4,-6.754,1,0.0629,0.454,0,0.319,0.313,94.071,4,198533,FALSE,E,major,E major, 47 | Fearless (Taylor's Version),FALSE,4/9/21,8,Tell Me Why (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.578,0.909,7,-3.669,1,0.0628,0.0222,0,0.333,0.541,100.023,4,200576,FALSE,G,major,G major, 48 | 1989,FALSE,10/27/14,15,You Are In Love,Taylor Swift,NA,TRUE,NA,NA,10/27/14,0.474,0.48,2,-8.894,1,0.0622,0.707,1.08e-4,0.0903,0.319,170.109,4,267107,FALSE,D,major,D major, 49 | evermore,FALSE,12/11/20,9,coney island,Taylor Swift,The National,FALSE,NA,1/18/21,12/11/20,0.537,0.537,8,-11.266,1,0.0617,0.819,9.04e-4,0.142,0.292,107.895,4,275320,FALSE,G#,major,G# major, 50 | Midnights,FALSE,10/21/22,5,"You're On Your Own, Kid",Taylor Swift,NA,FALSE,NA,NA,10/21/22,0.694,0.38,2,-10.307,1,0.0614,0.416,8.47e-6,0.126,0.376,120.044,4,194207,FALSE,D,major,D major, 51 | Midnights,FALSE,10/21/22,11,Karma,Taylor Swift,NA,FALSE,NA,NA,10/21/22,0.649,0.622,8,-7.054,1,0.0582,0.074,0,0.594,0.106,90.024,4,204852,TRUE,G#,major,G# major, 52 | Fearless (Taylor's Version),FALSE,4/9/21,19,The Other Side Of The Door (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.432,0.873,4,-4.367,1,0.0577,0.0279,0,0.167,0.629,163.936,4,238444,FALSE,E,major,E major, 53 | Lover,FALSE,8/23/19,16,ME!,Taylor Swift,"Brendon Urie, Panic! At The Disco",FALSE,NA,4/26/19,4/26/19,0.61,0.83,0,-4.105,1,0.0571,0.033,0,0.118,0.728,182.162,4,193000,FALSE,C,major,C major, 54 | Speak Now,FALSE,10/25/10,12,Haunted,Taylor Swift,NA,FALSE,NA,NA,10/25/10,0.436,0.95,5,-2.627,1,0.057,0.0826,1.08e-6,0.149,0.352,162.088,4,242080,FALSE,F,major,F major, 55 | Lover,FALSE,8/23/19,6,I Think He Knows,Taylor Swift,NA,FALSE,NA,NA,8/23/19,0.897,0.366,0,-8.029,1,0.0569,0.00889,3.53e-4,0.0715,0.416,100.003,4,173387,FALSE,C,major,C major, 56 | Midnights,FALSE,10/21/22,15,Bigger Than The Whole Sky,Taylor Swift,NA,TRUE,NA,NA,10/21/22,0.422,0.235,6,-12.379,1,0.0568,0.826,0.00648,0.115,0.068,165.71,4,218503,FALSE,F#,major,F# major, 57 | Red (Taylor's Version),FALSE,11/12/21,20,State Of Grace (Acoustic Version) [Taylor's Version],Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.445,0.131,7,-13.778,1,0.0564,0.663,1.57e-6,0.108,0.101,208.918,3,321640,FALSE,G,major,G major, 58 | Lover,FALSE,8/23/19,18,Daylight,Taylor Swift,NA,FALSE,NA,NA,8/23/19,0.557,0.496,0,-9.602,1,0.0563,0.808,1.73e-4,0.0772,0.265,149.983,4,293453,FALSE,C,major,C major, 59 | reputation,FALSE,11/10/17,2,End Game,Taylor Swift,"Ed Sheeran, Future",FALSE,NA,11/14/17,11/10/17,0.649,0.589,2,-6.237,1,0.0558,0.00845,0,0.108,0.151,159.073,4,244827,FALSE,D,major,D major, 60 | Lover,FALSE,8/23/19,14,You Need To Calm Down,Taylor Swift,NA,FALSE,NA,6/14/19,6/14/19,0.771,0.671,2,-5.617,1,0.0553,0.00929,0,0.0637,0.714,85.026,4,171360,FALSE,D,major,D major, 61 | Fearless,FALSE,11/11/08,11,Forever & Always,Taylor Swift,NA,FALSE,NA,NA,11/11/08,0.605,0.829,10,-4.993,1,0.0549,0.0907,0,0.106,0.514,128.012,4,225333,FALSE,A#,major,A# major, 62 | 1989,FALSE,10/27/14,14,Wonderland,Taylor Swift,NA,TRUE,2/17/15,NA,10/27/14,0.422,0.692,3,-5.447,1,0.0549,0.0493,2.57e-5,0.177,0.197,184.014,4,245560,FALSE,D#,major,D# major, 63 | 1989,FALSE,10/27/14,2,Blank Space,Taylor Swift,NA,FALSE,NA,11/10/14,10/27/14,0.76,0.703,5,-5.412,1,0.054,0.103,0,0.0913,0.57,95.997,4,231827,FALSE,F,major,F major, 64 | Lover,FALSE,8/23/19,4,The Man,Taylor Swift,NA,FALSE,NA,1/27/20,8/23/19,0.777,0.658,0,-5.191,1,0.054,0.0767,0,0.0901,0.633,110.048,4,190360,FALSE,C,major,C major, 65 | 1989,FALSE,10/27/14,7,I Wish You Would,Taylor Swift,NA,FALSE,NA,NA,10/27/14,0.653,0.893,0,-5.966,1,0.0538,0.0158,4.95e-5,0.102,0.513,118.035,4,207440,FALSE,C,major,C major, 66 | Taylor Swift,FALSE,10/24/06,15,Teardrops On My Guitar (Pop Version),Taylor Swift,NA,TRUE,NA,NA,3/18/08,0.459,0.753,10,-3.827,1,0.0537,0.0402,0,0.0863,0.483,199.997,4,179067,FALSE,A#,major,A# major, 67 | Red (Taylor's Version),FALSE,11/12/21,25,Message In A Bottle (Taylor's Version) [From The Vault],Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.622,0.791,7,-6.106,1,0.0535,0.00188,3.72e-6,0.083,0.494,115.915,4,225960,FALSE,G,major,G major, 68 | reputation,FALSE,11/10/17,12,Dress,Taylor Swift,NA,FALSE,NA,NA,11/10/17,0.719,0.469,0,-8.792,1,0.0533,0.0329,0,0.169,0.0851,120.085,4,230373,FALSE,C,major,C major, 69 | Fearless (Taylor's Version),FALSE,4/9/21,22,Mr. Perfectly Fine (Taylor's Version) [From The Vault],Taylor Swift,NA,FALSE,4/7/21,NA,4/7/21,0.66,0.817,11,-6.269,0,0.0521,0.162,0,0.0667,0.714,135.942,4,277592,FALSE,B,minor,B minor, 70 | folklore,FALSE,7/24/20,11,invisible string,Taylor Swift,NA,FALSE,NA,NA,7/24/20,0.651,0.459,4,-11.128,0,0.0507,0.859,6.63e-5,0.105,0.454,83.455,4,252880,FALSE,E,minor,E minor, 71 | Midnights,FALSE,10/21/22,12,Sweet Nothing,Taylor Swift,NA,FALSE,NA,NA,10/21/22,0.334,0.161,0,-14.875,1,0.0506,0.967,4.71e-5,0.115,0.396,176.747,4,188497,FALSE,C,major,C major, 72 | 1989,FALSE,10/27/14,10,How You Get The Girl,Taylor Swift,NA,FALSE,NA,NA,10/27/14,0.765,0.656,5,-6.112,1,0.0504,0.00364,0.00868,0.0918,0.545,119.997,4,247533,FALSE,F,major,F major, 73 | Fearless (Taylor's Version),FALSE,4/9/21,25,Don't You (Taylor's Version) [From The Vault],Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.563,0.473,3,-11.548,1,0.0503,0.514,1.2e-5,0.109,0.405,101.934,4,208609,FALSE,D#,major,D# major, 74 | Red (Taylor's Version),FALSE,11/12/21,1,State Of Grace (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.594,0.713,9,-5.314,1,0.0503,3.28e-4,0,0.114,0.328,129.958,4,295413,FALSE,A,major,A major, 75 | Red (Taylor's Version),FALSE,11/12/21,13,The Lucky One (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.686,0.571,4,-7.138,1,0.05,0.066,0,0.0608,0.538,117.889,4,240467,FALSE,E,major,E major, 76 | Lover,FALSE,8/23/19,11,London Boy,Taylor Swift,NA,FALSE,NA,NA,8/23/19,0.695,0.71,1,-6.639,1,0.05,0.0246,1.04e-4,0.133,0.557,157.925,4,190240,FALSE,C#,major,C# major, 77 | Lover,FALSE,8/23/19,8,Paper Rings,Taylor Swift,NA,FALSE,NA,NA,8/23/19,0.811,0.719,9,-6.553,1,0.0497,0.0129,1.36e-5,0.0742,0.865,103.979,4,222400,FALSE,A,major,A major, 78 | Red (Taylor's Version),FALSE,11/12/21,26,I Bet You Think About Me (Taylor's Version) [From The Vault],Taylor Swift,Chris Stapleton,FALSE,NA,NA,11/12/21,0.391,0.715,0,-4.516,1,0.0495,0.167,0,0.183,0.473,149.654,3,285107,TRUE,C,major,C major, 79 | folklore,FALSE,7/24/20,12,mad woman,Taylor Swift,NA,FALSE,NA,NA,7/24/20,0.593,0.7,3,-9.016,1,0.0492,0.67,6.77e-6,0.116,0.451,141.898,4,237267,TRUE,D#,major,D# major, 80 | evermore,FALSE,12/11/20,5,tolerate it,Taylor Swift,NA,FALSE,NA,NA,12/11/20,0.316,0.361,9,-10.381,1,0.0488,0.878,2.67e-5,0.0797,0.221,74.952,5,245440,TRUE,A,major,A major, 81 | Midnights,FALSE,10/21/22,10,Labyrinth,Taylor Swift,NA,FALSE,NA,NA,10/21/22,0.487,0.313,0,-15.434,1,0.0487,0.791,0.348,0.124,0.141,110.039,4,247962,FALSE,C,major,C major, 82 | Fearless (Taylor's Version),FALSE,4/9/21,5,White Horse (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.423,0.517,0,-7.377,1,0.0486,0.369,0,0.177,0.366,185.262,4,234516,FALSE,C,major,C major, 83 | Fearless (Taylor's Version),FALSE,4/9/21,10,The Way I Loved You (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.402,0.732,5,-4.665,1,0.0484,0.0033,0,0.108,0.472,161.032,4,243137,FALSE,F,major,F major, 84 | evermore,FALSE,12/11/20,13,marjorie,Taylor Swift,NA,FALSE,NA,NA,12/11/20,0.535,0.561,5,-11.609,1,0.0484,0.876,1.36e-4,0.115,0.287,96.103,4,257773,FALSE,F,major,F major, 85 | folklore,FALSE,7/24/20,1,the 1,Taylor Swift,NA,FALSE,10/9/20,NA,7/24/20,0.78,0.361,0,-6.942,1,0.048,0.75,7.14e-6,0.108,0.163,139.902,4,210240,TRUE,C,major,C major, 86 | Speak Now,FALSE,10/25/10,6,Mean,Taylor Swift,NA,FALSE,10/18/10,3/13/11,10/18/10,0.568,0.761,4,-3.987,1,0.0459,0.452,0,0.217,0.789,163.974,4,237733,FALSE,E,major,E major, 87 | Midnights,FALSE,10/21/22,3,Anti-Hero,Taylor Swift,NA,FALSE,NA,10/24/22,10/21/22,0.638,0.634,4,-6.582,1,0.0457,0.133,1.23e-6,0.152,0.519,96.953,4,200690,FALSE,E,major,E major, 88 | Fearless (Taylor's Version),FALSE,4/9/21,11,Forever & Always (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.598,0.821,10,-4.433,1,0.0447,0.0231,0,0.143,0.673,128.03,4,225328,FALSE,A#,major,A# major, 89 | Red,FALSE,10/22/12,2,Red,Taylor Swift,NA,FALSE,10/2/12,6/21/13,10/2/12,0.602,0.896,1,-4.267,0,0.0437,0.0773,8.87e-5,0.091,0.641,124.978,4,220827,FALSE,C#,minor,C# minor, 90 | Midnights,FALSE,10/21/22,4,Snow On The Beach,Taylor Swift,Lana Del Rey,FALSE,NA,NA,10/21/22,0.659,0.323,9,-13.425,1,0.0436,0.735,0.00321,0.116,0.154,110.007,4,256124,TRUE,A,major,A major, 91 | folklore,FALSE,7/24/20,2,cardigan,Taylor Swift,NA,FALSE,NA,7/27/20,7/24/20,0.612,0.58,0,-8.572,0,0.0419,0.525,4.14e-4,0.26,0.534,130.045,4,239560,FALSE,C,minor,C minor, 92 | evermore,FALSE,12/11/20,12,long story short,Taylor Swift,NA,FALSE,NA,NA,12/11/20,0.546,0.73,0,-7.704,1,0.0417,0.66,0.179,0.0972,0.573,157.895,4,215920,FALSE,C,major,C major, 93 | Fearless (Taylor's Version),FALSE,4/9/21,20,Today Was A Fairytale (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.482,0.779,7,-5.025,1,0.0409,0.0135,0,0.296,0.328,158.162,4,241822,FALSE,G,major,G major, 94 | evermore,FALSE,12/11/20,3,gold rush,Taylor Swift,NA,FALSE,NA,NA,12/11/20,0.512,0.462,9,-10.491,1,0.0408,0.83,0.166,0.121,0.353,112.05,4,185320,TRUE,A,major,A major, 95 | Red,FALSE,10/22/12,15,Starlight,Taylor Swift,NA,FALSE,NA,NA,10/22/12,0.649,0.777,9,-5.804,1,0.0406,0.0213,3.35e-4,0.221,0.587,126.018,4,217827,FALSE,A,major,A major, 96 | folklore,FALSE,7/24/20,3,the last great american dynasty,Taylor Swift,NA,FALSE,NA,NA,7/24/20,0.688,0.653,7,-8.558,1,0.0403,0.481,0.00414,0.106,0.701,147.991,4,231000,TRUE,G,major,G major, 97 | 1989,FALSE,10/27/14,3,Style,Taylor Swift,NA,FALSE,NA,2/9/15,10/27/14,0.588,0.791,7,-5.595,1,0.0402,0.00245,0.00258,0.118,0.487,94.933,4,231000,FALSE,G,major,G major, 98 | Lover,FALSE,8/23/19,5,The Archer,Taylor Swift,NA,FALSE,7/23/19,NA,7/23/19,0.292,0.574,0,-9.375,1,0.0401,0.12,0.00569,0.0663,0.166,124.344,4,211240,FALSE,C,major,C major, 99 | Lover,FALSE,8/23/19,17,It's Nice To Have A Friend,Taylor Swift,NA,FALSE,NA,NA,8/23/19,0.737,0.175,10,-9.912,1,0.0401,0.971,3.37e-4,0.171,0.545,70.008,4,150440,FALSE,A#,major,A# major, 100 | Speak Now,FALSE,10/25/10,7,The Story Of Us,Taylor Swift,NA,FALSE,NA,4/19/11,10/25/10,0.543,0.818,4,-3.611,1,0.0398,0.00478,3.68e-4,0.0357,0.683,139.898,4,265667,FALSE,E,major,E major, 101 | evermore,FALSE,12/11/20,17,it's time to go,Taylor Swift,NA,TRUE,NA,NA,12/11/20,0.592,0.41,0,-12.426,1,0.0397,0.801,1.39e-5,0.09,0.416,151.923,4,254640,FALSE,C,major,C major, 102 | 1989,FALSE,10/27/14,4,Out Of The Woods,Taylor Swift,NA,FALSE,10/14/14,1/19/16,10/14/14,0.553,0.841,7,-6.937,1,0.0396,7.43e-4,1.19e-5,0.341,0.338,92.008,4,235800,FALSE,G,major,G major, 103 | folklore,FALSE,7/24/20,16,hoax,Taylor Swift,NA,FALSE,NA,NA,7/24/20,0.676,0.178,8,-15.01,1,0.0394,0.964,7.9e-6,0.135,0.404,118.84,4,220040,FALSE,G#,major,G# major, 104 | Fearless,FALSE,11/11/08,8,Tell Me Why,Taylor Swift,NA,FALSE,NA,NA,11/11/08,0.601,0.852,7,-3.096,1,0.0386,0.0606,3.41e-6,0.37,0.513,100.015,4,200560,FALSE,G,major,G major, 105 | reputation,FALSE,11/10/17,4,Don't Blame Me,Taylor Swift,NA,FALSE,NA,NA,11/10/17,0.615,0.534,9,-6.719,0,0.0386,0.106,1.76e-5,0.0607,0.193,135.917,4,236413,FALSE,A,minor,A minor, 106 | Fearless,FALSE,11/11/08,6,You Belong With Me,Taylor Swift,NA,FALSE,11/4/08,4/18/09,11/4/08,0.687,0.771,6,-4.424,1,0.0384,0.164,2.46e-5,0.112,0.445,129.964,4,231147,FALSE,F#,major,F# major, 107 | Red (Taylor's Version),FALSE,11/12/21,22,Better Man (Taylor's Version) [From The Vault],Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.473,0.579,4,-5.824,1,0.0384,0.214,0,0.0877,0.255,73.942,4,297013,FALSE,E,major,E major, 108 | folklore,FALSE,7/24/20,17,the lakes,Taylor Swift,NA,TRUE,7/24/21,NA,7/24/20,0.313,0.258,2,-11.532,1,0.0383,0.842,1.3e-6,0.0948,0.268,179.947,4,211813,FALSE,D,major,D major, 109 | reputation,FALSE,11/10/17,10,King Of My Heart,Taylor Swift,NA,FALSE,NA,NA,11/10/17,0.675,0.703,0,-7.902,1,0.0382,0.00882,2.21e-6,0.0391,0.314,110.01,4,214320,FALSE,C,major,C major, 110 | Midnights,FALSE,10/21/22,2,Maroon,Taylor Swift,NA,FALSE,NA,NA,10/21/22,0.658,0.378,7,-8.3,1,0.0379,0.0593,0,0.0976,0.0382,108.034,4,218271,TRUE,G,major,G major, 111 | Red,FALSE,10/22/12,6,22,Taylor Swift,NA,FALSE,NA,3/12/13,10/22/12,0.658,0.729,7,-6.561,1,0.0378,0.00215,0.0013,0.0752,0.668,104.007,4,230133,FALSE,G,major,G major, 112 | evermore,FALSE,12/11/20,2,champagne problems,Taylor Swift,NA,FALSE,NA,NA,12/11/20,0.462,0.24,0,-12.077,1,0.0377,0.92,0,0.113,0.32,171.319,4,244000,TRUE,C,major,C major, 113 | evermore,FALSE,12/11/20,7,happiness,Taylor Swift,NA,FALSE,NA,NA,12/11/20,0.559,0.334,11,-10.733,1,0.0376,0.87,0,0.114,0.211,122.079,4,315147,TRUE,B,major,B major, 114 | Red (Taylor's Version),FALSE,11/12/21,2,Red (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.516,0.777,1,-4.908,0,0.0375,0.00108,1.62e-6,0.0761,0.408,125.047,4,223093,FALSE,C#,minor,C# minor, 115 | Fearless (Taylor's Version),FALSE,4/9/21,21,You All Over Me (Taylor's Version) [From The Vault],Taylor Swift,Maren Morris,FALSE,3/26/21,NA,3/26/21,0.599,0.494,2,-7.61,1,0.0372,0.816,0,0.101,0.44,142.893,4,220839,FALSE,D,major,D major, 116 | The Taylor Swift Holiday Collection,TRUE,10/14/07,1,Last Christmas,Taylor Swift,NA,FALSE,NA,NA,10/14/07,0.702,0.77,5,-5.319,1,0.0372,0.31,0,0.149,0.685,116.009,4,208253,FALSE,F,major,F major, 117 | Red,FALSE,10/22/12,13,The Lucky One,Taylor Swift,NA,FALSE,NA,NA,10/22/12,0.706,0.499,4,-7.675,1,0.0369,0.0371,0,0.132,0.545,117.937,4,240133,FALSE,E,major,E major, 118 | Taylor Swift,FALSE,10/24/06,14,A Perfectly Good Heart,Taylor Swift,NA,TRUE,NA,NA,3/18/08,0.483,0.751,4,-5.726,1,0.0365,0.00349,0,0.128,0.268,156.092,4,220147,FALSE,E,major,E major, 119 | Fearless (Taylor's Version),FALSE,4/9/21,24,That's When (Taylor's Version) [From The Vault],Taylor Swift,Keith Urban,FALSE,NA,NA,4/9/21,0.588,0.608,5,-7.062,1,0.0365,0.225,0,0.092,0.508,90.201,4,189496,FALSE,F,major,F major, 120 | Red,FALSE,10/22/12,4,I Knew You Were Trouble,Taylor Swift,NA,FALSE,NA,NA,10/22/12,0.622,0.469,3,-6.798,0,0.0363,0.00454,2.25e-6,0.0398,0.679,77.019,4,217973,FALSE,D#,minor,D# minor, 121 | Red (Taylor's Version),FALSE,11/12/21,14,Everything Has Changed (Taylor's Version),Taylor Swift,Ed Sheeran,FALSE,NA,NA,11/12/21,0.498,0.61,6,-5.098,1,0.0363,0.271,0,0.223,0.474,79.918,4,245427,FALSE,F#,major,F# major, 122 | folklore,FALSE,7/24/20,8,august,Taylor Swift,NA,FALSE,NA,NA,7/24/20,0.387,0.634,5,-9.222,1,0.0363,0.553,6.93e-5,0.0931,0.42,89.567,4,261920,FALSE,F,major,F major, 123 | Fearless,FALSE,11/11/08,13,Change,Taylor Swift,NA,FALSE,8/8/08,NA,8/8/08,0.548,0.756,5,-4.175,1,0.036,0.00471,0,0.117,0.224,96.038,4,281053,FALSE,F,major,F major, 124 | Fearless (Taylor's Version),FALSE,4/9/21,14,Jump Then Fall (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.572,0.69,2,-6.201,1,0.0358,0.0474,0,0.0897,0.624,160.111,4,237580,FALSE,D,major,D major, 125 | Red,FALSE,10/22/12,21,Red (Original Demo Recording),Taylor Swift,NA,TRUE,NA,NA,10/22/12,0.678,0.902,1,-4.659,0,0.0358,0.113,2.35e-4,0.0722,0.637,124.998,4,226280,FALSE,C#,minor,C# minor, 126 | Red (Taylor's Version),FALSE,11/12/21,15,Starlight (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.628,0.685,4,-5.864,1,0.0358,0.00324,0,0.18,0.605,126.014,4,220413,FALSE,E,major,E major, 127 | folklore,FALSE,7/24/20,10,illicit affairs,Taylor Swift,NA,FALSE,NA,NA,7/24/20,0.529,0.304,10,-10.567,1,0.0356,0.862,0,0.108,0.44,119.58,4,190893,FALSE,A#,major,A# major, 128 | The Taylor Swift Holiday Collection,TRUE,10/14/07,5,Christmas Must Be Something More,Taylor Swift,NA,FALSE,NA,NA,10/14/07,0.689,0.793,7,-4.838,1,0.0356,0.298,0,0.107,0.506,122.879,4,232147,FALSE,G,major,G major, 129 | Red,FALSE,10/22/12,20,Treacherous (Original Demo Recording),Taylor Swift,NA,TRUE,NA,NA,10/22/12,0.828,0.64,2,-8.248,1,0.0355,0.175,6.14e-5,0.0996,0.519,109.993,4,239720,FALSE,D,major,D major, 130 | reputation,FALSE,11/10/17,15,New Year's Day,Taylor Swift,NA,FALSE,NA,11/27/17,11/10/17,0.661,0.151,0,-12.864,1,0.0354,0.921,0,0.13,0.23,94.922,4,235467,FALSE,C,major,C major, 131 | evermore,FALSE,12/11/20,10,ivy,Taylor Swift,NA,FALSE,NA,NA,12/11/20,0.515,0.545,2,-9.277,1,0.0353,0.855,1.97e-5,0.0921,0.535,88.856,4,260440,TRUE,D,major,D major, 132 | Midnights,FALSE,10/21/22,14,The Great War,Taylor Swift,NA,TRUE,NA,NA,10/21/22,0.573,0.741,5,-8.987,1,0.0353,0.219,2.23e-5,0.0842,0.554,96.016,4,240356,FALSE,F,major,F major, 133 | folklore,FALSE,7/24/20,6,mirrorball,Taylor Swift,NA,FALSE,NA,NA,7/24/20,0.549,0.417,7,-10.064,1,0.035,0.686,1.9e-6,0.0604,0.347,110.137,4,208973,FALSE,G,major,G major, 134 | Fearless,FALSE,11/11/08,16,Forever & Always (Piano Version),Taylor Swift,NA,TRUE,NA,NA,10/26/09,0.592,0.16,9,-10.785,1,0.0347,0.852,0,0.116,0.294,118.929,4,267507,FALSE,A,major,A major, 135 | Fearless,FALSE,11/11/08,19,The Other Side Of The Door,Taylor Swift,NA,TRUE,NA,NA,10/26/09,0.476,0.816,4,-4.279,1,0.0347,0.169,0,0.238,0.469,163.847,4,237600,FALSE,E,major,E major, 136 | Speak Now,FALSE,10/25/10,14,Long Live,Taylor Swift,NA,FALSE,NA,NA,10/25/10,0.418,0.68,7,-4.3,1,0.0347,0.036,7.59e-5,0.114,0.142,204.12,4,317453,FALSE,G,major,G major, 137 | evermore,FALSE,12/11/20,11,cowboy like me,Taylor Swift,NA,FALSE,NA,NA,12/11/20,0.604,0.517,0,-9.014,1,0.0347,0.768,1.55e-4,0.123,0.511,127.967,4,275040,TRUE,C,major,C major, 138 | Fearless (Taylor's Version),FALSE,4/9/21,6,You Belong With Me (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.632,0.773,6,-4.856,1,0.0346,0.0623,0,0.0885,0.474,130.033,4,231124,FALSE,F#,major,F# major, 139 | 1989,FALSE,10/27/14,13,Clean,Taylor Swift,NA,FALSE,NA,NA,10/27/14,0.815,0.377,4,-7.754,1,0.0346,0.232,0,0.1,0.211,103.97,4,271000,FALSE,E,major,E major, 140 | Lover,FALSE,8/23/19,15,Afterglow,Taylor Swift,NA,FALSE,NA,NA,8/23/19,0.756,0.449,9,-8.746,1,0.0344,0.13,0,0.114,0.399,111.011,4,223293,FALSE,A,major,A major, 141 | Red (Taylor's Version),FALSE,11/12/21,4,I Knew You Were Trouble (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.584,0.557,6,-6.371,1,0.0342,0.0129,0,0.0576,0.767,154.008,4,219760,FALSE,F#,major,F# major, 142 | Fearless (Taylor's Version),FALSE,4/9/21,13,Change (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.499,0.815,5,-4.063,1,0.0341,1.91e-4,0,0.181,0.344,95.999,4,279360,FALSE,F,major,F major, 143 | Fearless,FALSE,11/11/08,15,Untouchable,Taylor Swift,NA,TRUE,NA,NA,10/26/09,0.419,0.454,5,-6.809,1,0.0335,0.0583,3.78e-6,0.0974,0.288,200.017,4,311040,FALSE,F,major,F major, 144 | Red,FALSE,10/22/12,3,Treacherous,Taylor Swift,NA,FALSE,NA,NA,10/22/12,0.705,0.621,2,-8.086,1,0.0334,0.101,4.3e-6,0.147,0.395,109.993,4,240773,FALSE,D,major,D major, 145 | Fearless,FALSE,11/11/08,10,The Way I Loved You,Taylor Swift,NA,FALSE,NA,NA,11/11/08,0.436,0.712,5,-3.689,1,0.0329,0.0322,0,0.111,0.379,160.886,4,244240,FALSE,F,major,F major, 146 | Fearless (Taylor's Version),FALSE,4/9/21,15,Untouchable (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.393,0.531,5,-6.112,1,0.0329,0.0181,0,0.169,0.224,200.391,4,312107,FALSE,F,major,F major, 147 | Red,FALSE,10/22/12,1,State Of Grace,Taylor Swift,NA,FALSE,10/16/12,NA,10/16/12,0.588,0.825,4,-5.882,1,0.0328,1.97e-4,0.00138,0.0885,0.397,129.968,4,295187,FALSE,E,major,E major, 148 | folklore,FALSE,7/24/20,5,my tears ricochet,Taylor Swift,NA,FALSE,NA,NA,7/24/20,0.456,0.265,0,-10.663,1,0.0328,0.814,0,0.0679,0.112,129.947,4,255893,FALSE,C,major,C major, 149 | Red (Taylor's Version),FALSE,11/12/21,19,Girl At Home (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.691,0.736,5,-6.974,1,0.0326,0.00955,1.88e-5,0.101,0.612,125.089,4,220520,FALSE,F,major,F major, 150 | Taylor Swift,FALSE,10/24/06,4,A Place In This World,Taylor Swift,NA,FALSE,NA,NA,10/24/06,0.576,0.777,9,-2.881,1,0.0324,0.051,0,0.32,0.428,115.028,4,199200,FALSE,A,major,A major, 151 | Speak Now,FALSE,10/25/10,17,Superman,Taylor Swift,NA,TRUE,11/8/10,NA,10/25/10,0.582,0.765,7,-3.648,1,0.0324,0.0268,1.95e-6,0.103,0.559,131.982,4,275960,FALSE,G,major,G major, 152 | Taylor Swift,FALSE,10/24/06,2,Picture To Burn,Taylor Swift,NA,FALSE,NA,2/3/08,10/24/06,0.658,0.877,7,-2.098,1,0.0323,0.173,0,0.0962,0.821,105.586,4,173067,FALSE,G,major,G major, 153 | 1989,FALSE,10/27/14,1,Welcome To New York,Taylor Swift,NA,FALSE,10/20/14,NA,10/20/14,0.789,0.634,7,-4.762,1,0.0323,0.0348,1.64e-6,0.302,0.658,116.992,4,212600,FALSE,G,major,G major, 154 | 1989,FALSE,10/27/14,5,All You Had To Do Was Stay,Taylor Swift,NA,FALSE,NA,NA,10/27/14,0.605,0.725,5,-5.729,1,0.0323,0.00201,2.02e-5,0.101,0.539,96.97,4,193293,FALSE,F,major,F major, 155 | folklore,FALSE,7/24/20,9,this is me trying,Taylor Swift,NA,FALSE,NA,NA,7/24/20,0.511,0.478,9,-9.837,1,0.0322,0.312,2.3e-5,0.147,0.408,135.882,4,195107,FALSE,A,major,A major, 156 | 1989,FALSE,10/27/14,11,This Love,Taylor Swift,NA,FALSE,NA,NA,10/27/14,0.481,0.435,4,-8.795,1,0.0321,0.678,0,0.0928,0.107,143.95,4,250093,FALSE,E,major,E major, 157 | Speak Now,FALSE,10/25/10,13,Last Kiss,Taylor Swift,NA,FALSE,NA,NA,10/25/10,0.371,0.341,10,-9.475,1,0.0319,0.57,3.09e-5,0.101,0.196,89.038,1,367133,FALSE,A#,major,A# major, 158 | The Taylor Swift Holiday Collection,TRUE,10/14/07,4,Silent Night,Taylor Swift,NA,FALSE,NA,NA,10/14/07,0.423,0.311,7,-7.581,1,0.0319,0.621,0,0.0987,0.134,119.546,3,209893,FALSE,G,major,G major, 159 | Fearless,FALSE,11/11/08,4,Hey Stephen,Taylor Swift,NA,FALSE,NA,NA,11/11/08,0.843,0.553,6,-7.348,1,0.0317,0.168,2.66e-6,0.107,0.825,115.997,4,254320,FALSE,F#,major,F# major, 160 | Red (Taylor's Version),FALSE,11/12/21,5,All Too Well (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.44,0.528,0,-7.809,1,0.0317,0.0171,0.00203,0.234,0.132,185.972,4,329160,FALSE,C,major,C major, 161 | Red,FALSE,10/22/12,17,The Moment I Knew,Taylor Swift,NA,TRUE,NA,NA,10/22/12,0.62,0.506,7,-7.327,1,0.0312,0.187,1.47e-5,0.102,0.275,126.015,4,285560,FALSE,G,major,G major, 162 | evermore,FALSE,12/11/20,4,'tis the damn season,Taylor Swift,NA,FALSE,NA,NA,12/11/20,0.575,0.434,5,-8.193,1,0.0312,0.735,6.59e-5,0.105,0.348,145.916,4,229840,FALSE,F,major,F major, 163 | Fearless (Taylor's Version),FALSE,4/9/21,3,Love Story (Taylor's Version),Taylor Swift,NA,FALSE,NA,2/12/21,2/12/21,0.627,0.792,2,-4.311,1,0.031,0.13,3.97e-6,0.0845,0.415,119.054,4,235767,FALSE,D,major,D major, 164 | Red (Taylor's Version),FALSE,11/12/21,17,The Moment I Knew (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.636,0.402,7,-7.855,1,0.031,0.0494,0,0.107,0.208,125.952,4,285280,FALSE,G,major,G major, 165 | Red (Taylor's Version),FALSE,11/12/21,21,Ronan (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.623,0.279,0,-10.802,1,0.031,0.661,0,0.193,0.38,116.04,4,264253,FALSE,C,major,C major, 166 | Red (Taylor's Version),FALSE,11/12/21,27,Forever Winter (Taylor's Version) [From The Vault],Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.611,0.552,7,-5.828,1,0.031,0.256,0,0.134,0.41,116.012,4,263533,FALSE,G,major,G major, 167 | Fearless,FALSE,11/11/08,3,Love Story,Taylor Swift,NA,FALSE,NA,9/12/08,9/12/08,0.618,0.736,2,-3.937,1,0.0308,0.157,0,0.073,0.307,118.982,4,235280,FALSE,D,major,D major, 168 | Fearless (Taylor's Version),FALSE,4/9/21,16,Forever & Always (Piano Version) [Taylor's Version],Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.546,0.273,9,-9.208,1,0.0308,0.819,0,0.137,0.271,118.753,4,267833,FALSE,A,major,A major, 169 | Speak Now,FALSE,10/25/10,2,Sparks Fly,Taylor Swift,NA,FALSE,NA,7/18/11,10/25/10,0.605,0.787,5,-3.002,1,0.0308,0.0396,1.42e-6,0.163,0.374,114.987,4,260933,FALSE,F,major,F major, 170 | evermore,FALSE,12/11/20,15,evermore,Taylor Swift,Bon Iver,FALSE,NA,NA,12/11/20,0.39,0.27,1,-10.673,1,0.0308,0.937,0.00227,0.111,0.32,125.177,5,304107,FALSE,C#,major,C# major, 171 | Speak Now,FALSE,10/25/10,8,Never Grow Up,Taylor Swift,NA,FALSE,NA,NA,10/25/10,0.714,0.305,2,-8.837,1,0.0305,0.819,0,0.174,0.554,124.91,4,290467,FALSE,D,major,D major, 172 | Red,FALSE,10/22/12,22,State Of Grace (Acoustic Version),Taylor Swift,NA,TRUE,NA,NA,10/22/12,0.626,0.118,7,-15.91,1,0.0305,0.664,1.69e-4,0.127,0.0682,105.597,4,323080,FALSE,G,major,G major, 173 | Speak Now,FALSE,10/25/10,4,Speak Now,Taylor Swift,NA,FALSE,10/4/10,NA,10/4/10,0.709,0.599,7,-3.734,1,0.0304,0.095,0,0.0973,0.735,118.975,4,240760,FALSE,G,major,G major, 174 | Taylor Swift,FALSE,10/24/06,11,Our Song,Taylor Swift,NA,FALSE,NA,9/9/07,10/24/06,0.668,0.672,2,-4.931,1,0.0303,0.111,0,0.329,0.539,89.011,4,201107,FALSE,D,major,D major, 175 | Speak Now,FALSE,10/25/10,3,Back To December,Taylor Swift,NA,FALSE,10/11/10,11/15/10,10/11/10,0.529,0.67,2,-4.663,1,0.0303,0.117,0,0.334,0.286,141.893,4,293027,FALSE,D,major,D major, 176 | Red (Taylor's Version),FALSE,11/12/21,30,All Too Well (10 Minute Version) [Taylor's Version] [From The Vault],Taylor Swift,NA,FALSE,11/15/21,NA,11/12/21,0.631,0.518,0,-8.771,1,0.0303,0.274,0,0.088,0.205,93.023,4,613027,TRUE,C,major,C major, 177 | Red (Taylor's Version),FALSE,11/12/21,18,Come Back...Be Here (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.46,0.632,4,-6.031,1,0.0302,0.0158,0,0.0822,0.399,79.846,4,223333,FALSE,E,major,E major, 178 | Fearless (Taylor's Version),FALSE,4/9/21,4,Hey Stephen (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.788,0.571,6,-6.135,1,0.0296,0.106,0,0.0934,0.797,115.99,4,254255,FALSE,F#,major,F# major, 179 | Speak Now,FALSE,10/25/10,1,Mine,Taylor Swift,NA,FALSE,NA,8/4/10,8/4/10,0.624,0.757,7,-2.94,1,0.0296,0.00265,1.87e-6,0.189,0.658,121.07,4,230707,FALSE,G,major,G major, 180 | Taylor Swift,FALSE,10/24/06,7,Tied Together With A Smile,Taylor Swift,NA,FALSE,NA,NA,10/24/06,0.479,0.578,2,-4.963,1,0.0294,0.525,0,0.0841,0.192,146.165,4,248107,FALSE,D,major,D major, 181 | Taylor Swift,FALSE,10/24/06,6,The Outside,Taylor Swift,NA,FALSE,NA,NA,10/24/06,0.589,0.805,5,-4.055,1,0.0293,0.00491,0,0.24,0.591,112.982,4,207107,FALSE,F,major,F major, 182 | Red (Taylor's Version),FALSE,11/12/21,28,Run (Taylor's Version) [From The Vault],Taylor Swift,Ed Sheeran,FALSE,NA,NA,11/12/21,0.61,0.488,3,-6.918,1,0.0293,0.817,0,0.312,0.443,125.039,4,240227,FALSE,D#,major,D# major, 183 | Taylor Swift,FALSE,10/24/06,10,Mary's Song (Oh My My My),Taylor Swift,NA,FALSE,NA,NA,10/24/06,0.403,0.627,2,-5.28,1,0.0292,0.0177,0,0.182,0.374,74.9,4,213080,FALSE,D,major,D major, 184 | Fearless,FALSE,11/11/08,12,The Best Day,Taylor Swift,NA,FALSE,NA,NA,11/11/08,0.664,0.447,1,-8.535,1,0.0292,0.611,1e-6,0.139,0.364,126.079,4,245347,FALSE,C#,major,C# major, 185 | Red,FALSE,10/22/12,12,Sad Beautiful Tragic,Taylor Swift,NA,FALSE,NA,NA,10/22/12,0.624,0.34,7,-12.411,1,0.029,0.632,0.0337,0.121,0.261,129.987,3,283680,FALSE,G,major,G major, 186 | Taylor Swift,FALSE,10/24/06,9,Should've Said No,Taylor Swift,NA,FALSE,NA,5/18/08,10/24/06,0.476,0.777,4,-3.771,0,0.0289,0.0103,0,0.196,0.472,167.964,4,242200,FALSE,E,minor,E minor, 187 | Speak Now,FALSE,10/25/10,9,Enchanted,Taylor Swift,NA,FALSE,NA,NA,10/25/10,0.455,0.623,8,-3.878,1,0.0288,0.0739,4.24e-4,0.146,0.208,163.893,4,352187,FALSE,G#,major,G# major, 188 | Red (Taylor's Version),FALSE,11/12/21,3,Treacherous (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.645,0.593,2,-6.506,1,0.0288,0.0344,1.27e-4,0.13,0.299,109.984,4,242720,FALSE,D,major,D major, 189 | Fearless (Taylor's Version),FALSE,4/9/21,7,Breathe (Taylor's Version),Taylor Swift,Colbie Caillat,FALSE,NA,NA,4/9/21,0.506,0.626,1,-6.066,1,0.0287,0.156,0,0.228,0.321,148.035,4,263377,FALSE,C#,major,C# major, 190 | Red,FALSE,10/22/12,14,Everything Has Changed,Taylor Swift,Ed Sheeran,FALSE,NA,7/16/13,10/22/12,0.624,0.458,6,-6.755,1,0.0287,0.36,3.89e-6,0.303,0.436,80.017,4,243933,FALSE,F#,major,F# major, 191 | The Taylor Swift Holiday Collection,TRUE,10/14/07,6,White Christmas,Taylor Swift,NA,FALSE,NA,NA,10/14/07,0.562,0.392,9,-9.014,1,0.0287,0.587,0,0.224,0.514,145.792,4,154000,FALSE,A,major,A major, 192 | folklore,FALSE,7/24/20,13,epiphany,Taylor Swift,NA,FALSE,NA,NA,7/24/20,0.317,0.254,1,-13.679,1,0.0286,0.727,3.26e-4,0.0867,0.103,93.933,4,289747,FALSE,C#,major,C# major, 193 | Fearless (Taylor's Version),FALSE,4/9/21,9,You're Not Sorry (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.506,0.444,6,-4.99,1,0.0285,0.0538,0,0.123,0.241,133.873,4,261843,FALSE,F#,major,F# major, 194 | The Taylor Swift Holiday Collection,TRUE,10/14/07,2,Christmases When You Were Mine,Taylor Swift,NA,FALSE,NA,NA,10/14/07,0.625,0.206,7,-12.568,1,0.0285,0.646,0,0.0915,0.392,79.466,4,184253,FALSE,G,major,G major, 195 | Speak Now,FALSE,10/25/10,15,Ours,Taylor Swift,NA,TRUE,NA,11/22/11,10/25/10,0.608,0.569,0,-7.227,1,0.0284,0.537,0,0.097,0.238,159.845,4,237920,FALSE,C,major,C major, 196 | Red,FALSE,10/22/12,19,Girl At Home,Taylor Swift,NA,TRUE,NA,NA,10/22/12,0.733,0.59,5,-5.86,1,0.0284,0.272,0,0.136,0.633,125.048,4,220320,FALSE,F,major,F major, 197 | Fearless (Taylor's Version),FALSE,4/9/21,18,SuperStar (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.454,0.555,2,-6.69,1,0.0283,0.157,0,0.343,0.171,171.862,4,263865,FALSE,D,major,D major, 198 | Fearless (Taylor's Version),FALSE,4/9/21,1,Fearless (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.569,0.741,5,-4.963,1,0.0282,0.0122,0,0.333,0.43,100.118,4,241592,FALSE,F,major,F major, 199 | Fearless (Taylor's Version),FALSE,4/9/21,12,The Best Day (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.642,0.576,1,-8.524,1,0.0282,0.488,0,0.159,0.469,125,4,245290,FALSE,C#,major,C# major, 200 | evermore,FALSE,12/11/20,16,right where you left me,Taylor Swift,NA,TRUE,NA,NA,12/11/20,0.581,0.619,10,-6.524,1,0.0282,0.769,1.06e-4,0.117,0.645,137.915,4,245027,FALSE,A#,major,A# major, 201 | Red (Taylor's Version),FALSE,11/12/21,6,22 (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.642,0.695,7,-5.62,1,0.0281,4.43e-4,1.02e-5,0.0753,0.642,103.984,4,230960,FALSE,G,major,G major, 202 | Red (Taylor's Version),FALSE,11/12/21,29,The Very First Night (Taylor's Version) [From The Vault],Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.678,0.733,7,-5.025,1,0.0281,0.00115,0,0.104,0.581,121.009,4,200280,FALSE,G,major,G major, 203 | Speak Now,FALSE,10/25/10,5,Dear John,Taylor Swift,NA,FALSE,NA,NA,10/25/10,0.589,0.47,4,-5.32,1,0.028,0.166,1.45e-6,0.112,0.102,119.386,3,403920,FALSE,E,major,E major, 204 | Red (Taylor's Version),FALSE,11/12/21,10,The Last Time (Taylor's Version),Taylor Swift,Gary Lightbody,FALSE,NA,NA,11/12/21,0.502,0.534,2,-5.954,1,0.0278,0.0399,0,0.0977,0.155,94.05,4,299080,FALSE,D,major,D major, 205 | folklore,FALSE,7/24/20,7,seven,Taylor Swift,NA,FALSE,NA,NA,7/24/20,0.602,0.494,4,-10.813,1,0.0277,0.888,2.59e-5,0.0902,0.374,94.955,4,208907,FALSE,E,major,E major, 206 | Red,FALSE,10/22/12,10,The Last Time,Taylor Swift,Gary Lightbody,FALSE,NA,11/4/13,10/22/12,0.505,0.443,2,-7.039,1,0.0276,0.035,3.15e-6,0.0921,0.123,94.069,4,298293,FALSE,D,major,D major, 207 | Fearless,FALSE,11/11/08,9,You're Not Sorry,Taylor Swift,NA,FALSE,10/28/08,NA,10/28/08,0.459,0.459,6,-4.126,1,0.0275,0.13,2.3e-6,0.113,0.281,134.018,4,261800,FALSE,F#,major,F# major, 208 | Red (Taylor's Version),FALSE,11/12/21,12,Sad Beautiful Tragic (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.601,0.406,7,-11.827,1,0.0275,0.622,9.19e-5,0.133,0.232,130.059,3,284773,FALSE,G,major,G major, 209 | Red (Taylor's Version),FALSE,11/12/21,23,Nothing New (Taylor's Version) [From The Vault],Taylor Swift,Phoebe Bridgers,FALSE,NA,NA,11/12/21,0.606,0.377,0,-9.455,1,0.0275,0.817,0,0.154,0.446,101.96,4,258813,FALSE,C,major,C major, 210 | folklore,FALSE,7/24/20,4,exile,Taylor Swift,Bon Iver,FALSE,NA,8/3/20,7/24/20,0.31,0.374,6,-8.464,1,0.0275,0.761,2.46e-5,0.11,0.16,75.938,4,285640,FALSE,F#,major,F# major, 211 | Red (Taylor's Version),FALSE,11/12/21,16,Begin Again (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.519,0.527,7,-7.673,1,0.0274,0.075,0,0.132,0.267,78.915,4,238867,FALSE,G,major,G major, 212 | Fearless,FALSE,11/11/08,1,Fearless,Taylor Swift,NA,FALSE,10/14/08,1/3/10,10/14/08,0.598,0.714,5,-4.397,1,0.0273,0.0364,1.04e-6,0.31,0.467,99.979,4,242000,FALSE,F,major,F major, 213 | Fearless,FALSE,11/11/08,7,Breathe,Taylor Swift,Colbie Caillat,FALSE,10/21/08,NA,10/21/08,0.498,0.486,1,-7.384,1,0.0273,0.373,0,0.113,0.17,147.952,4,263987,FALSE,C#,major,C# major, 214 | Fearless (Taylor's Version),FALSE,4/9/21,2,Fifteen (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.559,0.646,7,-4.61,1,0.0272,0.116,0,0.32,0.289,95.466,4,294419,FALSE,G,major,G major, 215 | Fearless,FALSE,11/11/08,14,Jump Then Fall,Taylor Swift,NA,TRUE,NA,NA,10/26/09,0.618,0.687,2,-5.737,1,0.0271,0.111,0,0.0693,0.82,79.991,4,237067,FALSE,D,major,D major, 216 | The Taylor Swift Holiday Collection,TRUE,10/14/07,3,Santa Baby,Taylor Swift,NA,FALSE,NA,NA,10/14/07,0.759,0.509,5,-6.593,1,0.0271,0.159,0,0.153,0.75,107.948,4,158600,FALSE,F,major,F major, 217 | Red,FALSE,10/22/12,7,I Almost Do,Taylor Swift,NA,FALSE,NA,NA,10/22/12,0.567,0.481,4,-7.357,1,0.027,0.0173,4.13e-5,0.182,0.233,145.865,4,242573,FALSE,E,major,E major, 218 | Fearless (Taylor's Version),FALSE,4/9/21,17,Come In With The Rain (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.476,0.564,2,-5.677,1,0.0269,0.0406,0,0.102,0.167,143.929,4,237339,FALSE,D,major,D major, 219 | Taylor Swift,FALSE,10/24/06,5,Cold As You,Taylor Swift,NA,FALSE,NA,NA,10/24/06,0.418,0.482,5,-5.769,1,0.0266,0.217,0,0.123,0.261,175.558,4,239013,FALSE,F,major,F major, 220 | Speak Now,FALSE,10/25/10,16,If This Was A Movie,Taylor Swift,NA,TRUE,11/8/10,NA,10/25/10,0.511,0.719,4,-3.457,1,0.0265,0.166,4.54e-6,0.34,0.289,147.852,4,234547,FALSE,E,major,E major, 221 | Red (Taylor's Version),FALSE,11/12/21,7,I Almost Do (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.511,0.559,4,-6.587,1,0.0264,0.0167,0,0.113,0.248,145.88,4,244587,FALSE,E,major,E major, 222 | evermore,FALSE,12/11/20,6,"no body, no crime",Taylor Swift,HAIM,FALSE,NA,1/11/21,12/11/20,0.546,0.613,7,-7.589,0,0.0264,0.418,0,0.103,0.535,79.015,4,215627,FALSE,G,minor,G minor, 223 | evermore,FALSE,12/11/20,8,dorothea,Taylor Swift,NA,FALSE,NA,NA,12/11/20,0.605,0.488,4,-8.322,1,0.0264,0.696,0,0.129,0.354,119.966,4,225880,FALSE,E,major,E major, 224 | Fearless,FALSE,11/11/08,2,Fifteen,Taylor Swift,NA,FALSE,NA,8/30/09,11/11/08,0.559,0.636,7,-4.4,1,0.0263,0.0728,0,0.144,0.214,95.485,4,294347,FALSE,G,major,G major, 225 | Fearless (Taylor's Version),FALSE,4/9/21,23,We Were Happy (Taylor's Version) [From The Vault],Taylor Swift,NA,FALSE,NA,NA,4/9/21,0.609,0.373,0,-8.819,1,0.0263,0.849,0,0.0779,0.13,106.007,4,244237,FALSE,C,major,C major, 226 | Red,FALSE,10/22/12,16,Begin Again,Taylor Swift,NA,FALSE,9/25/12,10/1/12,9/25/12,0.53,0.526,7,-8.349,1,0.0263,0.199,3.52e-6,0.232,0.323,79.025,4,237613,FALSE,G,major,G major, 227 | Speak Now,FALSE,10/25/10,11,Innocent,Taylor Swift,NA,FALSE,NA,NA,10/25/10,0.552,0.608,1,-5.29,0,0.0259,0.186,0,0.125,0.17,134.05,4,302253,FALSE,C#,minor,C# minor, 228 | Fearless,FALSE,11/11/08,17,Come In With The Rain,Taylor Swift,NA,TRUE,NA,NA,10/26/09,0.527,0.475,2,-6.205,1,0.0258,0.0868,4.34e-6,0.248,0.224,143.962,4,238107,FALSE,D,major,D major, 229 | Fearless,FALSE,11/11/08,5,White Horse,Taylor Swift,NA,FALSE,NA,12/7/08,11/11/08,0.585,0.346,0,-8.014,1,0.0256,0.302,0,0.102,0.195,92.564,4,234440,FALSE,C,major,C major, 230 | folklore,FALSE,7/24/20,14,betty,Taylor Swift,NA,FALSE,NA,8/17/20,7/24/20,0.595,0.376,0,-8.767,1,0.0256,0.589,0,0.0989,0.476,95.946,4,294520,TRUE,C,major,C major, 231 | Red,FALSE,10/22/12,18,Come Back...Be Here,Taylor Swift,NA,TRUE,NA,NA,10/22/12,0.483,0.548,4,-6.291,1,0.0254,0.00471,2.01e-6,0.0997,0.217,79.926,4,222707,FALSE,E,major,E major, 232 | Taylor Swift,FALSE,10/24/06,1,Tim McGraw,Taylor Swift,NA,FALSE,NA,6/19/06,6/19/06,0.58,0.491,0,-6.462,1,0.0251,0.575,0,0.121,0.425,76.009,4,232107,FALSE,C,major,C major, 233 | Red (Taylor's Version),FALSE,11/12/21,9,Stay Stay Stay (Taylor's Version),Taylor Swift,NA,FALSE,NA,NA,11/12/21,0.693,0.681,0,-7.039,1,0.025,0.0848,0,0.0768,0.663,100.02,4,205787,FALSE,C,major,C major, 234 | Taylor Swift,FALSE,10/24/06,8,Stay Beautiful,Taylor Swift,NA,FALSE,NA,NA,10/24/06,0.594,0.629,8,-4.919,1,0.0246,0.0868,0,0.137,0.504,131.597,4,236053,FALSE,G#,major,G# major, 235 | Red,FALSE,10/22/12,9,Stay Stay Stay,Taylor Swift,NA,FALSE,NA,NA,10/22/12,0.729,0.748,0,-6.67,1,0.0245,0.307,1.29e-6,0.09,0.928,99.981,4,204827,FALSE,C,major,C major, 236 | Taylor Swift,FALSE,10/24/06,13,Invisible,Taylor Swift,NA,TRUE,NA,NA,3/18/08,0.612,0.394,7,-5.723,1,0.0243,0.637,0,0.147,0.233,96.001,4,203227,FALSE,G,major,G major, 237 | Red,FALSE,10/22/12,5,All Too Well,Taylor Swift,NA,FALSE,NA,NA,10/22/12,0.602,0.609,0,-7.803,1,0.0243,0.0382,2e-4,0.127,0.337,93.06,4,327893,FALSE,C,major,C major, 238 | Fearless,FALSE,11/11/08,18,SuperStar,Taylor Swift,NA,TRUE,NA,NA,10/26/09,0.61,0.531,2,-6.123,1,0.0239,0.367,0,0.351,0.228,85.979,4,261453,FALSE,D,major,D major, 239 | Taylor Swift,FALSE,10/24/06,3,Teardrops On My Guitar,Taylor Swift,NA,FALSE,NA,2/19/07,10/24/06,0.621,0.417,10,-6.941,1,0.0231,0.288,0,0.119,0.289,99.953,4,203040,FALSE,A#,major,A# major, -------------------------------------------------------------------------------- /dashboard/theme.scss: -------------------------------------------------------------------------------- 1 | /*-- scss:defaults --*/ 2 | @import url('https://fonts.googleapis.com/css?family=IBM+Plex+Sans&display=swap'); 3 | @import url('https://fonts.googleapis.com/css?family=Yomogi&display=swap'); 4 | 5 | 6 | $title-font:"Yomogi"; 7 | $main-color:#401487; 8 | $font-color:white; 9 | $light-color:#F4F5FB; 10 | $main-font:"IBM Plex Sans"; 11 | 12 | 13 | .sidebar form { 14 | flex-direction:column; 15 | } 16 | 17 | .bslib-card .card-body+.card-body { 18 | height: 70%!important; 19 | } 20 | 21 | .navbar-logo { 22 | border-radius:100%!important; 23 | } 24 | 25 | .navbar-title { 26 | font-family:Yomogi; 27 | text-transform:uppercase; 28 | } 29 | 30 | th:first-child, 31 | td:first-child { 32 | display: none!important; 33 | } 34 | 35 | 36 | 37 | h1 { 38 | font-family: Yomogi; 39 | } 40 | 41 | .quarto-dashboard.dashboard-sidebar>.page-layout-custom>.bslib-sidebar-layout>.sidebar { 42 | padding-left: 1em; 43 | padding-right: 1em; 44 | background: $light-color; 45 | } 46 | 47 | input[type='range'] { 48 | accent-color:$main-color!important; 49 | } 50 | 51 | body { 52 | font-family:$main-font!important; 53 | } 54 | 55 | .navbar { 56 | background-color:$main-color!important; 57 | } 58 | 59 | 60 | .quarto-dashboard .card.valuebox.bslib-card.bg-light { 61 | background-color: $light-color!important; 62 | color: $main-color!important; 63 | } 64 | 65 | .observablehq--number { 66 | font-family:$main-font!important; 67 | color:$main-color!important; 68 | font-size:50px; 69 | } 70 | 71 | .card-header { 72 | background:$light-color; 73 | color:#272727; 74 | } 75 | 76 | .quarto-dashboard .bslib-value-box .value-box-showcase .bi { 77 | font-size:3em; 78 | } 79 | 80 | .bslib-value-box .value-box-area { 81 | padding:0px!important; 82 | } 83 | 84 | .html-fill-container>.html-fill-item { 85 | overflow:hidden; 86 | } 87 | -------------------------------------------------------------------------------- /dashboard/tswift.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tashapiro/intro-observable/2c44e8f252d49de3a5271a096c458331ae908f01/dashboard/tswift.jpeg -------------------------------------------------------------------------------- /presentation/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tashapiro/intro-observable/2c44e8f252d49de3a5271a096c458331ae908f01/presentation/.DS_Store -------------------------------------------------------------------------------- /presentation/images/headshot.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tashapiro/intro-observable/2c44e8f252d49de3a5271a096c458331ae908f01/presentation/images/headshot.jpeg -------------------------------------------------------------------------------- /presentation/images/intro-observable.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | format: 3 | revealjs: 4 | footer: "Tanya Shapiro | R-Ladies Philly" 5 | slide-number: true 6 | theme: "theme.scss" 7 | embed-resources: true 8 | self-contained: TRUE 9 | --- 10 | 11 | ## {#TitleSlide data-menu-title="TitleSlide" background-color="white" background-image="images/title-slide.png" background-position="center center" background-size="cover"} 12 | 13 | ```{=html} 14 | 21 | ``` 22 | ## Hi, I'm Tanya! 23 | 24 | ```{=html} 25 |
26 | 27 |
28 |
    29 |
  • Independent data consultant, specializing in data analysis and visualization design
  • 30 |
  • Previously worked in insurance industry
  • 31 |
  • Fell in love with R via #TidyTuesday
  • 32 |
  • Data geek based in St. Pete, FL
  • 33 |
34 |
35 |
36 | 43 | ``` 44 | ## Data Viz Learning Journey 45 | 46 | Started off with building visuals in R. Then I wanted to get into more interactive visuals... 47 | 48 | ::: columns 49 | ::: {.column style="width:40%;"} 50 | ![Actual footage of me stressing out.](https://media.giphy.com/media/76Nahzjx4Y6WXP2APE/giphy.gif) 51 | ::: 52 | 53 | ::: {.column style="width:60%;"} 54 | - What is node.js? 55 | - What is D3? And svg? 56 | - What IDE should I use? 57 | - Omg where do I start 58 | ::: 59 | ::: 60 | 61 | ## A Less Intimidating Way to Start... 62 | 63 | Observable Notebooks = my new playground for data viz experiments 64 | 65 | ::: columns 66 | ::: {.column style="width:40%;"} 67 | ![Going from overwhelmed to excited](https://media.giphy.com/media/arKAOElAQxszte7YfJ/giphy.gif) 68 | ::: 69 | 70 | ::: {.column style="width:60%;"} 71 | - No fuss with installation 72 | - Accessible community, lots of resources 73 | - Observable Plot felt familiar 74 | - Integrate easily with my Quarto site 75 | ::: 76 | ::: 77 | 78 | ## What is Observable Plot? 79 | 80 | ::: columns 81 | ::: {.column style="width:50%;font-size:35px;"} 82 | - Free and open-source JS library for data viz 83 | 84 | - Under the hood - D3! 85 | 86 | - Inspired by the grammar of graphics 87 | 88 | - Create visuals with layered marks 89 | 90 | - Combine with inputs to create interactive plots 91 | ::: 92 | 93 | ::: {.column style="width:50%"} 94 | ![](images/observable.png){fig-align="center"} 95 | ::: 96 | ::: 97 | 98 | ## Example of Observable Plot 99 | 100 | #### Big Tech Stock Prices 101 | 102 | ```{ojs} 103 | 104 | stocks = FileAttachment("data/big_tech_stock_prices.csv").csv({ typed: true }) 105 | 106 | tickers = ["AAPL","ADBE","IBM","META", "GOOGL"] 107 | 108 | viewof selectStock = Inputs.select(tickers, {label:"Stock", value: "GOOGL"}) 109 | 110 | 111 | filtered_stocks = stocks.filter(d => d.stock_symbol === selectStock) 112 | 113 | Plot.plot({ 114 | width:1000, 115 | x: {label:''}, 116 | y: {label:'Close ($)', grid:true}, 117 | marks: [ 118 | Plot.areaY(filtered_stocks, {x: "date", y: "close", fillOpacity: 0.3, fill:'#4197FF'}), 119 | Plot.lineY(filtered_stocks, {x: "date", y: "close", stroke:'#4197FF'}), 120 | Plot.ruleY([0]) 121 | ] 122 | }) 123 | 124 | ``` 125 | 126 | ## Goals for Today 127 | 128 | ```{=html} 129 |
130 |
Learn about Observable Notebooks
131 |
Work with Observable Plot
132 |
Add in dynamic inputs
133 |
Combine Observable with Quarto
134 |
135 | ``` 136 | ## Let's Get Plotting! 137 | 138 | Reroute to this [Observable Notebook](https://observablehq.com/d/f002f055d6fa2f8d). 139 | 140 | ::: {.div style="width:100%;display:flex;justify-content:center;"} 141 | [![To The Notebook!](https://media.giphy.com/media/l0CPbjYU8276ahvLa/giphy.gif){fig-align="center" width="800"}](https://observablehq.com/d/f002f055d6fa2f8d) 142 | ::: 143 | -------------------------------------------------------------------------------- /presentation/images/intro.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tashapiro/intro-observable/2c44e8f252d49de3a5271a096c458331ae908f01/presentation/images/intro.webp -------------------------------------------------------------------------------- /presentation/images/observable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tashapiro/intro-observable/2c44e8f252d49de3a5271a096c458331ae908f01/presentation/images/observable.png -------------------------------------------------------------------------------- /presentation/images/observable.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tashapiro/intro-observable/2c44e8f252d49de3a5271a096c458331ae908f01/presentation/images/observable.webp -------------------------------------------------------------------------------- /presentation/images/philly.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tashapiro/intro-observable/2c44e8f252d49de3a5271a096c458331ae908f01/presentation/images/philly.jpeg -------------------------------------------------------------------------------- /presentation/images/title-slide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tashapiro/intro-observable/2c44e8f252d49de3a5271a096c458331ae908f01/presentation/images/title-slide.png -------------------------------------------------------------------------------- /presentation/intro-observable.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | format: 3 | revealjs: 4 | footer: "Tanya Shapiro | R-Ladies Philly" 5 | slide-number: true 6 | theme: "theme.scss" 7 | embed-resources: true 8 | self-contained: TRUE 9 | --- 10 | 11 | ## {#TitleSlide data-menu-title="TitleSlide" background-color="white" background-image="images/title-slide.png" background-position="center center" background-size="cover"} 12 | 13 | ```{=html} 14 | 21 | ``` 22 | ## Hi, I'm Tanya! 23 | 24 | ```{=html} 25 |
26 | 27 |
28 |
    29 |
  • Independent data consultant, specializing in data analysis and visualization design
  • 30 |
  • Previously worked in insurance industry
  • 31 |
  • Fell in love with R via #TidyTuesday
  • 32 |
  • Data geek based in St. Pete, FL
  • 33 |
34 |
35 |
36 | 43 | ``` 44 | ## Data Viz Learning Journey 45 | 46 | Started off with building visuals in R. Then I wanted to get into more interactive visuals... 47 | 48 | ::: columns 49 | ::: {.column style="width:40%;"} 50 | ![Actual footage of me stressing out.](https://media.giphy.com/media/76Nahzjx4Y6WXP2APE/giphy.gif) 51 | ::: 52 | 53 | ::: {.column style="width:60%;"} 54 | - What is node.js? 55 | - What is D3? And svg? 56 | - What IDE should I use? 57 | - Omg where do I start 58 | ::: 59 | ::: 60 | 61 | ## A Less Intimidating Way to Start... 62 | 63 | Observable Notebooks = my new playground for data viz experiments 64 | 65 | ::: columns 66 | ::: {.column style="width:40%;"} 67 | ![Going from overwhelmed to excited](https://media.giphy.com/media/arKAOElAQxszte7YfJ/giphy.gif) 68 | ::: 69 | 70 | ::: {.column style="width:60%;"} 71 | - No fuss with installation 72 | - Accessible community, lots of resources 73 | - Observable Plot felt familiar 74 | - Integrate easily with my Quarto site 75 | ::: 76 | ::: 77 | 78 | ## What is Observable Plot? 79 | 80 | ::: columns 81 | ::: {.column style="width:50%;font-size:35px;"} 82 | - Free and open-source JS library for data viz 83 | 84 | - Under the hood - D3! 85 | 86 | - Inspired by the grammar of graphics 87 | 88 | - Create visuals with layered marks 89 | 90 | - Combine with inputs to create interactive plots 91 | ::: 92 | 93 | ::: {.column style="width:50%"} 94 | ![](images/observable.png){fig-align="center"} 95 | ::: 96 | ::: 97 | 98 | ## Example of Observable Plot 99 | 100 | #### Big Tech Stock Prices 101 | 102 | ```{ojs} 103 | 104 | stocks = FileAttachment("data/big_tech_stock_prices.csv").csv({ typed: true }) 105 | 106 | tickers = ["AAPL","ADBE","IBM","META", "GOOGL"] 107 | 108 | viewof selectStock = Inputs.select(tickers, {label:"Stock", value: "GOOGL"}) 109 | 110 | 111 | filtered_stocks = stocks.filter(d => d.stock_symbol === selectStock) 112 | 113 | Plot.plot({ 114 | width:1000, 115 | x: {label:''}, 116 | y: {label:'Close ($)', grid:true}, 117 | marks: [ 118 | Plot.areaY(filtered_stocks, {x: "date", y: "close", fillOpacity: 0.3, fill:'#4197FF'}), 119 | Plot.lineY(filtered_stocks, {x: "date", y: "close", stroke:'#4197FF'}), 120 | Plot.ruleY([0]) 121 | ] 122 | }) 123 | 124 | ``` 125 | 126 | ## Goals for Today 127 | 128 | ```{=html} 129 |
130 |
Learn about Observable Notebooks
131 |
Work with Observable Plot
132 |
Add in dynamic inputs
133 |
Combine Observable with Quarto
134 |
135 | ``` 136 | ## Let's Get Plotting! 137 | 138 | Reroute to this [Observable Notebook](https://observablehq.com/d/f002f055d6fa2f8d). 139 | 140 | ::: {.div style="width:100%;display:flex;justify-content:center;"} 141 | [![To The Notebook!](https://media.giphy.com/media/l0CPbjYU8276ahvLa/giphy.gif){fig-align="center" width="800"}](https://observablehq.com/d/f002f055d6fa2f8d) 142 | ::: 143 | -------------------------------------------------------------------------------- /presentation/theme.scss: -------------------------------------------------------------------------------- 1 | /*-- scss:defaults --*/ 2 | 3 | @import url("https://use.fontawesome.com/releases/v6.4.2/css/all.css"); 4 | @import url('https://fonts.googleapis.com/css?family=Roboto&display=swap'); 5 | 6 | 7 | $font-family-sans-serif: "Roboto"; 8 | $primary-color: #54278f; 9 | $code-color:#49BCFF; 10 | 11 | .blocks { 12 | display: flex; 13 | flex-wrap: wrap; 14 | justify-content: center; 15 | } 16 | 17 | .block i { 18 | margin-right: 20px; 19 | margin-left: 10px; 20 | } 21 | 22 | figcaption { 23 | font-size: 0.6em!important; 24 | color: grey!important; 25 | } 26 | 27 | .block { 28 | border-radius:10px; 29 | height:60px; 30 | flex: 1 1 calc(50% - 20px); /* changed */ 31 | background-color: $primary-color; 32 | color: white; 33 | margin: 1%; 34 | padding: 2%; 35 | opacity: 1; 36 | transition: opacity 0.5s; 37 | display: flex; /* added */ 38 | justify-content: left; /* added */ 39 | align-items: center; /* added */ 40 | text-align: left; /* added */ 41 | } 42 | 43 | .block code { 44 | color: whitesmoke !important; 45 | } 46 | 47 | .about-me { 48 | margin-top:50px; 49 | vertical-align:middle;} 50 | 51 | 52 | .social-icons { 53 | display: flex; 54 | justify-content: center; 55 | margin-top: 20px; 56 | font-size:1.2em; 57 | width:50%; 58 | margin-left:200px; 59 | } 60 | 61 | .social-icons a { 62 | display: inline-flex; 63 | margin-left:15px; 64 | margin-right:15px; 65 | } 66 | 67 | 68 | 69 | .about-me img { 70 | width: 220px!important; 71 | height:220px; 72 | clip-path: inset(0 0 0 0 round 50%); 73 | } 74 | 75 | --------------------------------------------------------------------------------