From 5b085e3dae6ab5e661b4daef8f89e87b0bf277cb Mon Sep 17 00:00:00 2001 From: ro-g-m Date: Wed, 20 Dec 2023 15:02:52 -0300 Subject: [PATCH] se agrega imagen para el post --- .../comportamiento.Rmd | 4 +- .../comportamiento.html | 204 +- .../header-attrs-2.21/header-attrs.js | 12 + .../htmlwidgets-1.6.2/htmlwidgets.js | 901 +++++++ .../plotly-binding-4.10.1/plotly.js | 939 ++++++++ _posts/2023-12-14-comportamiento/graf_tabl.R | 1 - _posts/2023-12-14-comportamiento/imag1.PNG | Bin 0 -> 24231 bytes docs/index.html | 2088 ++++++++--------- .../header-attrs-2.20/header-attrs.js | 12 + .../htmlwidgets-1.6.1/htmlwidgets.js | 901 +++++++ .../plotly-binding-4.10.2/plotly.js | 941 ++++++++ .../posts/2023-12-14-comportamiento/imag1.PNG | Bin 0 -> 24231 bytes .../2023-12-14-comportamiento/index.html | 208 +- docs/posts/posts.json | 6 +- .../htmlwidgets-1.6.2/htmlwidgets.js | 901 +++++++ .../site_libs/plotly-binding-4.10.1/plotly.js | 939 ++++++++ 16 files changed, 6832 insertions(+), 1225 deletions(-) create mode 100644 _posts/2023-12-14-comportamiento/comportamiento_files/header-attrs-2.21/header-attrs.js create mode 100644 _posts/2023-12-14-comportamiento/comportamiento_files/htmlwidgets-1.6.2/htmlwidgets.js create mode 100644 _posts/2023-12-14-comportamiento/comportamiento_files/plotly-binding-4.10.1/plotly.js create mode 100644 _posts/2023-12-14-comportamiento/imag1.PNG create mode 100644 docs/posts/2023-12-14-comportamiento/comportamiento_files/header-attrs-2.20/header-attrs.js create mode 100644 docs/posts/2023-12-14-comportamiento/comportamiento_files/htmlwidgets-1.6.1/htmlwidgets.js create mode 100644 docs/posts/2023-12-14-comportamiento/comportamiento_files/plotly-binding-4.10.2/plotly.js create mode 100644 docs/posts/2023-12-14-comportamiento/imag1.PNG create mode 100644 docs/site_libs/htmlwidgets-1.6.2/htmlwidgets.js create mode 100644 docs/site_libs/plotly-binding-4.10.1/plotly.js diff --git a/_posts/2023-12-14-comportamiento/comportamiento.Rmd b/_posts/2023-12-14-comportamiento/comportamiento.Rmd index 4e2067e..cf88e97 100644 --- a/_posts/2023-12-14-comportamiento/comportamiento.Rmd +++ b/_posts/2023-12-14-comportamiento/comportamiento.Rmd @@ -1,11 +1,13 @@ --- title: "Comportamiento turístico de las personas, años 2021-2022" -descripción: "Publicación de informes anuales con estimaciones sobre comportamiento turístico de las personas en base a la Encuesta de Viajes y Turismo de los Hogares (EVyTH)" +description: "Publicación de informes anuales con estimaciones sobre comportamiento turístico de las personas en base a la Encuesta de Viajes y Turismo de los Hogares (EVyTH)" date: 2023-12-14 output: distill::distill_article: self_contained: false draft: false +preview: "imag1.PNG" + --- ```{r setup, include=FALSE} diff --git a/_posts/2023-12-14-comportamiento/comportamiento.html b/_posts/2023-12-14-comportamiento/comportamiento.html index cca3948..8821729 100644 --- a/_posts/2023-12-14-comportamiento/comportamiento.html +++ b/_posts/2023-12-14-comportamiento/comportamiento.html @@ -90,6 +90,7 @@ Comportamiento turístico de las personas, años 2021-2022 + @@ -99,22 +100,24 @@ + + @@ -1471,9 +1474,9 @@ - - - + + + @@ -1499,7 +1502,7 @@ @@ -1512,7 +1515,7 @@

Comportamiento turístico de las personas, años 2021-2022

- +

Publicación de informes anuales con estimaciones sobre comportamiento turístico de las personas en base a la Encuesta de Viajes y Turismo de los Hogares (EVyTH)

@@ -1528,28 +1531,40 @@

Resultados

Entre los principales resultados que se presentan en el informe, se puede observar la evolución de la proporción de personas que realizaron al menos un viaje en el año 2021/2022. Los datos tanto de 2021 como de 2022, muestran una mejoría respecto al 20202, aunque aún no se recuperan los valores previos a la pandemia de covid.

Porcentaje de población residente en grandes aglomerados urbanos, de un año y más, que realizó al menos un viaje con pernocte. Años 2006, 2010 - 2022.

-
- +
+

Los datos se presentan desagregados por sexo, rango etario, región de origen de las personas, quintil de ingresos, y máximo nivel educativo alcanzado, lo que permite aproximar el análisis de viajes realizados, destino de los viajes o motivo de no viaje según variables socioeconómicas.

Los resultados demuestran que los quintiles más bajos no alcanzaron a recuperar los valores previos a la pandemia, como sí lo hizo el quintil más alto.

Porcentaje de población residente en grandes aglomerados urbanos, de un año y más, que realizó al menos un viaje con pernocte, según ingreso per cápita familiar. Años 2006, 2010-2022.

-
- +
+

Entre los principales motivos de no viaje en 2022 pondera la falta de dinero con un 49,2%, frente al 36,4% de 2021 cuando recién se estaba activando el turismo post pandemia y explica el aumento en la declaración de “otros motivos” que llegó a 19,4% en 2021, frente a 1,1% en 2022.

Distribución porcentual de población residente en grandes aglomerados urbanos, de un año y más, que no realizaron viajes con pernocte, según motivo principal por el que no viajaron. Años 2010 - 2022.

-
+
- - - +
+ + - - - + - + @@ -2145,11 +2175,11 @@

Recursos disponibles

Para recibir las novedades del SINTA escribíle al bot de Telegram de la DNMyE SintIA: @RDatinaBot 🤖

-
+

    -
  1. Por ejemplo, el relevamiento realizado en 2023 obtiene una base de datos con ventana de observación en 2022.↩︎

  2. -
  3. En el año 2020 se dispusieron por decreto medidas de emergencia sanitaria para contener la propagación del virus COVID-19 que restringían las salidas de la población. Estas medidas se mantuvieron desde Marzo de 2020 hasta el 31 de Diciembre de 2021.↩︎

  4. +
  5. Por ejemplo, el relevamiento realizado en 2023 obtiene una base de datos con ventana de observación en 2022.↩︎

  6. +
  7. En el año 2020 se dispusieron por decreto medidas de emergencia sanitaria para contener la propagación del virus COVID-19 que restringían las salidas de la población. Estas medidas se mantuvieron desde Marzo de 2020 hasta el 31 de Diciembre de 2021.↩︎

diff --git a/_posts/2023-12-14-comportamiento/comportamiento_files/header-attrs-2.21/header-attrs.js b/_posts/2023-12-14-comportamiento/comportamiento_files/header-attrs-2.21/header-attrs.js new file mode 100644 index 0000000..dd57d92 --- /dev/null +++ b/_posts/2023-12-14-comportamiento/comportamiento_files/header-attrs-2.21/header-attrs.js @@ -0,0 +1,12 @@ +// Pandoc 2.9 adds attributes on both header and div. We remove the former (to +// be compatible with the behavior of Pandoc < 2.8). +document.addEventListener('DOMContentLoaded', function(e) { + var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); + var i, h, a; + for (i = 0; i < hs.length; i++) { + h = hs[i]; + if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 + a = h.attributes; + while (a.length > 0) h.removeAttribute(a[0].name); + } +}); diff --git a/_posts/2023-12-14-comportamiento/comportamiento_files/htmlwidgets-1.6.2/htmlwidgets.js b/_posts/2023-12-14-comportamiento/comportamiento_files/htmlwidgets-1.6.2/htmlwidgets.js new file mode 100644 index 0000000..1067d02 --- /dev/null +++ b/_posts/2023-12-14-comportamiento/comportamiento_files/htmlwidgets-1.6.2/htmlwidgets.js @@ -0,0 +1,901 @@ +(function() { + // If window.HTMLWidgets is already defined, then use it; otherwise create a + // new object. This allows preceding code to set options that affect the + // initialization process (though none currently exist). + window.HTMLWidgets = window.HTMLWidgets || {}; + + // See if we're running in a viewer pane. If not, we're in a web browser. + var viewerMode = window.HTMLWidgets.viewerMode = + /\bviewer_pane=1\b/.test(window.location); + + // See if we're running in Shiny mode. If not, it's a static document. + // Note that static widgets can appear in both Shiny and static modes, but + // obviously, Shiny widgets can only appear in Shiny apps/documents. + var shinyMode = window.HTMLWidgets.shinyMode = + typeof(window.Shiny) !== "undefined" && !!window.Shiny.outputBindings; + + // We can't count on jQuery being available, so we implement our own + // version if necessary. + function querySelectorAll(scope, selector) { + if (typeof(jQuery) !== "undefined" && scope instanceof jQuery) { + return scope.find(selector); + } + if (scope.querySelectorAll) { + return scope.querySelectorAll(selector); + } + } + + function asArray(value) { + if (value === null) + return []; + if ($.isArray(value)) + return value; + return [value]; + } + + // Implement jQuery's extend + function extend(target /*, ... */) { + if (arguments.length == 1) { + return target; + } + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + target[prop] = source[prop]; + } + } + } + return target; + } + + // IE8 doesn't support Array.forEach. + function forEach(values, callback, thisArg) { + if (values.forEach) { + values.forEach(callback, thisArg); + } else { + for (var i = 0; i < values.length; i++) { + callback.call(thisArg, values[i], i, values); + } + } + } + + // Replaces the specified method with the return value of funcSource. + // + // Note that funcSource should not BE the new method, it should be a function + // that RETURNS the new method. funcSource receives a single argument that is + // the overridden method, it can be called from the new method. The overridden + // method can be called like a regular function, it has the target permanently + // bound to it so "this" will work correctly. + function overrideMethod(target, methodName, funcSource) { + var superFunc = target[methodName] || function() {}; + var superFuncBound = function() { + return superFunc.apply(target, arguments); + }; + target[methodName] = funcSource(superFuncBound); + } + + // Add a method to delegator that, when invoked, calls + // delegatee.methodName. If there is no such method on + // the delegatee, but there was one on delegator before + // delegateMethod was called, then the original version + // is invoked instead. + // For example: + // + // var a = { + // method1: function() { console.log('a1'); } + // method2: function() { console.log('a2'); } + // }; + // var b = { + // method1: function() { console.log('b1'); } + // }; + // delegateMethod(a, b, "method1"); + // delegateMethod(a, b, "method2"); + // a.method1(); + // a.method2(); + // + // The output would be "b1", "a2". + function delegateMethod(delegator, delegatee, methodName) { + var inherited = delegator[methodName]; + delegator[methodName] = function() { + var target = delegatee; + var method = delegatee[methodName]; + + // The method doesn't exist on the delegatee. Instead, + // call the method on the delegator, if it exists. + if (!method) { + target = delegator; + method = inherited; + } + + if (method) { + return method.apply(target, arguments); + } + }; + } + + // Implement a vague facsimilie of jQuery's data method + function elementData(el, name, value) { + if (arguments.length == 2) { + return el["htmlwidget_data_" + name]; + } else if (arguments.length == 3) { + el["htmlwidget_data_" + name] = value; + return el; + } else { + throw new Error("Wrong number of arguments for elementData: " + + arguments.length); + } + } + + // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex + function escapeRegExp(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + } + + function hasClass(el, className) { + var re = new RegExp("\\b" + escapeRegExp(className) + "\\b"); + return re.test(el.className); + } + + // elements - array (or array-like object) of HTML elements + // className - class name to test for + // include - if true, only return elements with given className; + // if false, only return elements *without* given className + function filterByClass(elements, className, include) { + var results = []; + for (var i = 0; i < elements.length; i++) { + if (hasClass(elements[i], className) == include) + results.push(elements[i]); + } + return results; + } + + function on(obj, eventName, func) { + if (obj.addEventListener) { + obj.addEventListener(eventName, func, false); + } else if (obj.attachEvent) { + obj.attachEvent(eventName, func); + } + } + + function off(obj, eventName, func) { + if (obj.removeEventListener) + obj.removeEventListener(eventName, func, false); + else if (obj.detachEvent) { + obj.detachEvent(eventName, func); + } + } + + // Translate array of values to top/right/bottom/left, as usual with + // the "padding" CSS property + // https://developer.mozilla.org/en-US/docs/Web/CSS/padding + function unpackPadding(value) { + if (typeof(value) === "number") + value = [value]; + if (value.length === 1) { + return {top: value[0], right: value[0], bottom: value[0], left: value[0]}; + } + if (value.length === 2) { + return {top: value[0], right: value[1], bottom: value[0], left: value[1]}; + } + if (value.length === 3) { + return {top: value[0], right: value[1], bottom: value[2], left: value[1]}; + } + if (value.length === 4) { + return {top: value[0], right: value[1], bottom: value[2], left: value[3]}; + } + } + + // Convert an unpacked padding object to a CSS value + function paddingToCss(paddingObj) { + return paddingObj.top + "px " + paddingObj.right + "px " + paddingObj.bottom + "px " + paddingObj.left + "px"; + } + + // Makes a number suitable for CSS + function px(x) { + if (typeof(x) === "number") + return x + "px"; + else + return x; + } + + // Retrieves runtime widget sizing information for an element. + // The return value is either null, or an object with fill, padding, + // defaultWidth, defaultHeight fields. + function sizingPolicy(el) { + var sizingEl = document.querySelector("script[data-for='" + el.id + "'][type='application/htmlwidget-sizing']"); + if (!sizingEl) + return null; + var sp = JSON.parse(sizingEl.textContent || sizingEl.text || "{}"); + if (viewerMode) { + return sp.viewer; + } else { + return sp.browser; + } + } + + // @param tasks Array of strings (or falsy value, in which case no-op). + // Each element must be a valid JavaScript expression that yields a + // function. Or, can be an array of objects with "code" and "data" + // properties; in this case, the "code" property should be a string + // of JS that's an expr that yields a function, and "data" should be + // an object that will be added as an additional argument when that + // function is called. + // @param target The object that will be "this" for each function + // execution. + // @param args Array of arguments to be passed to the functions. (The + // same arguments will be passed to all functions.) + function evalAndRun(tasks, target, args) { + if (tasks) { + forEach(tasks, function(task) { + var theseArgs = args; + if (typeof(task) === "object") { + theseArgs = theseArgs.concat([task.data]); + task = task.code; + } + var taskFunc = tryEval(task); + if (typeof(taskFunc) !== "function") { + throw new Error("Task must be a function! Source:\n" + task); + } + taskFunc.apply(target, theseArgs); + }); + } + } + + // Attempt eval() both with and without enclosing in parentheses. + // Note that enclosing coerces a function declaration into + // an expression that eval() can parse + // (otherwise, a SyntaxError is thrown) + function tryEval(code) { + var result = null; + try { + result = eval("(" + code + ")"); + } catch(error) { + if (!(error instanceof SyntaxError)) { + throw error; + } + try { + result = eval(code); + } catch(e) { + if (e instanceof SyntaxError) { + throw error; + } else { + throw e; + } + } + } + return result; + } + + function initSizing(el) { + var sizing = sizingPolicy(el); + if (!sizing) + return; + + var cel = document.getElementById("htmlwidget_container"); + if (!cel) + return; + + if (typeof(sizing.padding) !== "undefined") { + document.body.style.margin = "0"; + document.body.style.padding = paddingToCss(unpackPadding(sizing.padding)); + } + + if (sizing.fill) { + document.body.style.overflow = "hidden"; + document.body.style.width = "100%"; + document.body.style.height = "100%"; + document.documentElement.style.width = "100%"; + document.documentElement.style.height = "100%"; + cel.style.position = "absolute"; + var pad = unpackPadding(sizing.padding); + cel.style.top = pad.top + "px"; + cel.style.right = pad.right + "px"; + cel.style.bottom = pad.bottom + "px"; + cel.style.left = pad.left + "px"; + el.style.width = "100%"; + el.style.height = "100%"; + + return { + getWidth: function() { return cel.getBoundingClientRect().width; }, + getHeight: function() { return cel.getBoundingClientRect().height; } + }; + + } else { + el.style.width = px(sizing.width); + el.style.height = px(sizing.height); + + return { + getWidth: function() { return cel.getBoundingClientRect().width; }, + getHeight: function() { return cel.getBoundingClientRect().height; } + }; + } + } + + // Default implementations for methods + var defaults = { + find: function(scope) { + return querySelectorAll(scope, "." + this.name); + }, + renderError: function(el, err) { + var $el = $(el); + + this.clearError(el); + + // Add all these error classes, as Shiny does + var errClass = "shiny-output-error"; + if (err.type !== null) { + // use the classes of the error condition as CSS class names + errClass = errClass + " " + $.map(asArray(err.type), function(type) { + return errClass + "-" + type; + }).join(" "); + } + errClass = errClass + " htmlwidgets-error"; + + // Is el inline or block? If inline or inline-block, just display:none it + // and add an inline error. + var display = $el.css("display"); + $el.data("restore-display-mode", display); + + if (display === "inline" || display === "inline-block") { + $el.hide(); + if (err.message !== "") { + var errorSpan = $("").addClass(errClass); + errorSpan.text(err.message); + $el.after(errorSpan); + } + } else if (display === "block") { + // If block, add an error just after the el, set visibility:none on the + // el, and position the error to be on top of the el. + // Mark it with a unique ID and CSS class so we can remove it later. + $el.css("visibility", "hidden"); + if (err.message !== "") { + var errorDiv = $("
").addClass(errClass).css("position", "absolute") + .css("top", el.offsetTop) + .css("left", el.offsetLeft) + // setting width can push out the page size, forcing otherwise + // unnecessary scrollbars to appear and making it impossible for + // the element to shrink; so use max-width instead + .css("maxWidth", el.offsetWidth) + .css("height", el.offsetHeight); + errorDiv.text(err.message); + $el.after(errorDiv); + + // Really dumb way to keep the size/position of the error in sync with + // the parent element as the window is resized or whatever. + var intId = setInterval(function() { + if (!errorDiv[0].parentElement) { + clearInterval(intId); + return; + } + errorDiv + .css("top", el.offsetTop) + .css("left", el.offsetLeft) + .css("maxWidth", el.offsetWidth) + .css("height", el.offsetHeight); + }, 500); + } + } + }, + clearError: function(el) { + var $el = $(el); + var display = $el.data("restore-display-mode"); + $el.data("restore-display-mode", null); + + if (display === "inline" || display === "inline-block") { + if (display) + $el.css("display", display); + $(el.nextSibling).filter(".htmlwidgets-error").remove(); + } else if (display === "block"){ + $el.css("visibility", "inherit"); + $(el.nextSibling).filter(".htmlwidgets-error").remove(); + } + }, + sizing: {} + }; + + // Called by widget bindings to register a new type of widget. The definition + // object can contain the following properties: + // - name (required) - A string indicating the binding name, which will be + // used by default as the CSS classname to look for. + // - initialize (optional) - A function(el) that will be called once per + // widget element; if a value is returned, it will be passed as the third + // value to renderValue. + // - renderValue (required) - A function(el, data, initValue) that will be + // called with data. Static contexts will cause this to be called once per + // element; Shiny apps will cause this to be called multiple times per + // element, as the data changes. + window.HTMLWidgets.widget = function(definition) { + if (!definition.name) { + throw new Error("Widget must have a name"); + } + if (!definition.type) { + throw new Error("Widget must have a type"); + } + // Currently we only support output widgets + if (definition.type !== "output") { + throw new Error("Unrecognized widget type '" + definition.type + "'"); + } + // TODO: Verify that .name is a valid CSS classname + + // Support new-style instance-bound definitions. Old-style class-bound + // definitions have one widget "object" per widget per type/class of + // widget; the renderValue and resize methods on such widget objects + // take el and instance arguments, because the widget object can't + // store them. New-style instance-bound definitions have one widget + // object per widget instance; the definition that's passed in doesn't + // provide renderValue or resize methods at all, just the single method + // factory(el, width, height) + // which returns an object that has renderValue(x) and resize(w, h). + // This enables a far more natural programming style for the widget + // author, who can store per-instance state using either OO-style + // instance fields or functional-style closure variables (I guess this + // is in contrast to what can only be called C-style pseudo-OO which is + // what we required before). + if (definition.factory) { + definition = createLegacyDefinitionAdapter(definition); + } + + if (!definition.renderValue) { + throw new Error("Widget must have a renderValue function"); + } + + // For static rendering (non-Shiny), use a simple widget registration + // scheme. We also use this scheme for Shiny apps/documents that also + // contain static widgets. + window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || []; + // Merge defaults into the definition; don't mutate the original definition. + var staticBinding = extend({}, defaults, definition); + overrideMethod(staticBinding, "find", function(superfunc) { + return function(scope) { + var results = superfunc(scope); + // Filter out Shiny outputs, we only want the static kind + return filterByClass(results, "html-widget-output", false); + }; + }); + window.HTMLWidgets.widgets.push(staticBinding); + + if (shinyMode) { + // Shiny is running. Register the definition with an output binding. + // The definition itself will not be the output binding, instead + // we will make an output binding object that delegates to the + // definition. This is because we foolishly used the same method + // name (renderValue) for htmlwidgets definition and Shiny bindings + // but they actually have quite different semantics (the Shiny + // bindings receive data that includes lots of metadata that it + // strips off before calling htmlwidgets renderValue). We can't + // just ignore the difference because in some widgets it's helpful + // to call this.renderValue() from inside of resize(), and if + // we're not delegating, then that call will go to the Shiny + // version instead of the htmlwidgets version. + + // Merge defaults with definition, without mutating either. + var bindingDef = extend({}, defaults, definition); + + // This object will be our actual Shiny binding. + var shinyBinding = new Shiny.OutputBinding(); + + // With a few exceptions, we'll want to simply use the bindingDef's + // version of methods if they are available, otherwise fall back to + // Shiny's defaults. NOTE: If Shiny's output bindings gain additional + // methods in the future, and we want them to be overrideable by + // HTMLWidget binding definitions, then we'll need to add them to this + // list. + delegateMethod(shinyBinding, bindingDef, "getId"); + delegateMethod(shinyBinding, bindingDef, "onValueChange"); + delegateMethod(shinyBinding, bindingDef, "onValueError"); + delegateMethod(shinyBinding, bindingDef, "renderError"); + delegateMethod(shinyBinding, bindingDef, "clearError"); + delegateMethod(shinyBinding, bindingDef, "showProgress"); + + // The find, renderValue, and resize are handled differently, because we + // want to actually decorate the behavior of the bindingDef methods. + + shinyBinding.find = function(scope) { + var results = bindingDef.find(scope); + + // Only return elements that are Shiny outputs, not static ones + var dynamicResults = results.filter(".html-widget-output"); + + // It's possible that whatever caused Shiny to think there might be + // new dynamic outputs, also caused there to be new static outputs. + // Since there might be lots of different htmlwidgets bindings, we + // schedule execution for later--no need to staticRender multiple + // times. + if (results.length !== dynamicResults.length) + scheduleStaticRender(); + + return dynamicResults; + }; + + // Wrap renderValue to handle initialization, which unfortunately isn't + // supported natively by Shiny at the time of this writing. + + shinyBinding.renderValue = function(el, data) { + Shiny.renderDependencies(data.deps); + // Resolve strings marked as javascript literals to objects + if (!(data.evals instanceof Array)) data.evals = [data.evals]; + for (var i = 0; data.evals && i < data.evals.length; i++) { + window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]); + } + if (!bindingDef.renderOnNullValue) { + if (data.x === null) { + el.style.visibility = "hidden"; + return; + } else { + el.style.visibility = "inherit"; + } + } + if (!elementData(el, "initialized")) { + initSizing(el); + + elementData(el, "initialized", true); + if (bindingDef.initialize) { + var rect = el.getBoundingClientRect(); + var result = bindingDef.initialize(el, rect.width, rect.height); + elementData(el, "init_result", result); + } + } + bindingDef.renderValue(el, data.x, elementData(el, "init_result")); + evalAndRun(data.jsHooks.render, elementData(el, "init_result"), [el, data.x]); + }; + + // Only override resize if bindingDef implements it + if (bindingDef.resize) { + shinyBinding.resize = function(el, width, height) { + // Shiny can call resize before initialize/renderValue have been + // called, which doesn't make sense for widgets. + if (elementData(el, "initialized")) { + bindingDef.resize(el, width, height, elementData(el, "init_result")); + } + }; + } + + Shiny.outputBindings.register(shinyBinding, bindingDef.name); + } + }; + + var scheduleStaticRenderTimerId = null; + function scheduleStaticRender() { + if (!scheduleStaticRenderTimerId) { + scheduleStaticRenderTimerId = setTimeout(function() { + scheduleStaticRenderTimerId = null; + window.HTMLWidgets.staticRender(); + }, 1); + } + } + + // Render static widgets after the document finishes loading + // Statically render all elements that are of this widget's class + window.HTMLWidgets.staticRender = function() { + var bindings = window.HTMLWidgets.widgets || []; + forEach(bindings, function(binding) { + var matches = binding.find(document.documentElement); + forEach(matches, function(el) { + var sizeObj = initSizing(el, binding); + + var getSize = function(el) { + if (sizeObj) { + return {w: sizeObj.getWidth(), h: sizeObj.getHeight()} + } else { + var rect = el.getBoundingClientRect(); + return {w: rect.width, h: rect.height} + } + }; + + if (hasClass(el, "html-widget-static-bound")) + return; + el.className = el.className + " html-widget-static-bound"; + + var initResult; + if (binding.initialize) { + var size = getSize(el); + initResult = binding.initialize(el, size.w, size.h); + elementData(el, "init_result", initResult); + } + + if (binding.resize) { + var lastSize = getSize(el); + var resizeHandler = function(e) { + var size = getSize(el); + if (size.w === 0 && size.h === 0) + return; + if (size.w === lastSize.w && size.h === lastSize.h) + return; + lastSize = size; + binding.resize(el, size.w, size.h, initResult); + }; + + on(window, "resize", resizeHandler); + + // This is needed for cases where we're running in a Shiny + // app, but the widget itself is not a Shiny output, but + // rather a simple static widget. One example of this is + // an rmarkdown document that has runtime:shiny and widget + // that isn't in a render function. Shiny only knows to + // call resize handlers for Shiny outputs, not for static + // widgets, so we do it ourselves. + if (window.jQuery) { + window.jQuery(document).on( + "shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets", + resizeHandler + ); + window.jQuery(document).on( + "hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets", + resizeHandler + ); + } + + // This is needed for the specific case of ioslides, which + // flips slides between display:none and display:block. + // Ideally we would not have to have ioslide-specific code + // here, but rather have ioslides raise a generic event, + // but the rmarkdown package just went to CRAN so the + // window to getting that fixed may be long. + if (window.addEventListener) { + // It's OK to limit this to window.addEventListener + // browsers because ioslides itself only supports + // such browsers. + on(document, "slideenter", resizeHandler); + on(document, "slideleave", resizeHandler); + } + } + + var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']"); + if (scriptData) { + var data = JSON.parse(scriptData.textContent || scriptData.text); + // Resolve strings marked as javascript literals to objects + if (!(data.evals instanceof Array)) data.evals = [data.evals]; + for (var k = 0; data.evals && k < data.evals.length; k++) { + window.HTMLWidgets.evaluateStringMember(data.x, data.evals[k]); + } + binding.renderValue(el, data.x, initResult); + evalAndRun(data.jsHooks.render, initResult, [el, data.x]); + } + }); + }); + + invokePostRenderHandlers(); + } + + + function has_jQuery3() { + if (!window.jQuery) { + return false; + } + var $version = window.jQuery.fn.jquery; + var $major_version = parseInt($version.split(".")[0]); + return $major_version >= 3; + } + + /* + / Shiny 1.4 bumped jQuery from 1.x to 3.x which means jQuery's + / on-ready handler (i.e., $(fn)) is now asyncronous (i.e., it now + / really means $(setTimeout(fn)). + / https://jquery.com/upgrade-guide/3.0/#breaking-change-document-ready-handlers-are-now-asynchronous + / + / Since Shiny uses $() to schedule initShiny, shiny>=1.4 calls initShiny + / one tick later than it did before, which means staticRender() is + / called renderValue() earlier than (advanced) widget authors might be expecting. + / https://github.com/rstudio/shiny/issues/2630 + / + / For a concrete example, leaflet has some methods (e.g., updateBounds) + / which reference Shiny methods registered in initShiny (e.g., setInputValue). + / Since leaflet is privy to this life-cycle, it knows to use setTimeout() to + / delay execution of those methods (until Shiny methods are ready) + / https://github.com/rstudio/leaflet/blob/18ec981/javascript/src/index.js#L266-L268 + / + / Ideally widget authors wouldn't need to use this setTimeout() hack that + / leaflet uses to call Shiny methods on a staticRender(). In the long run, + / the logic initShiny should be broken up so that method registration happens + / right away, but binding happens later. + */ + function maybeStaticRenderLater() { + if (shinyMode && has_jQuery3()) { + window.jQuery(window.HTMLWidgets.staticRender); + } else { + window.HTMLWidgets.staticRender(); + } + } + + if (document.addEventListener) { + document.addEventListener("DOMContentLoaded", function() { + document.removeEventListener("DOMContentLoaded", arguments.callee, false); + maybeStaticRenderLater(); + }, false); + } else if (document.attachEvent) { + document.attachEvent("onreadystatechange", function() { + if (document.readyState === "complete") { + document.detachEvent("onreadystatechange", arguments.callee); + maybeStaticRenderLater(); + } + }); + } + + + window.HTMLWidgets.getAttachmentUrl = function(depname, key) { + // If no key, default to the first item + if (typeof(key) === "undefined") + key = 1; + + var link = document.getElementById(depname + "-" + key + "-attachment"); + if (!link) { + throw new Error("Attachment " + depname + "/" + key + " not found in document"); + } + return link.getAttribute("href"); + }; + + window.HTMLWidgets.dataframeToD3 = function(df) { + var names = []; + var length; + for (var name in df) { + if (df.hasOwnProperty(name)) + names.push(name); + if (typeof(df[name]) !== "object" || typeof(df[name].length) === "undefined") { + throw new Error("All fields must be arrays"); + } else if (typeof(length) !== "undefined" && length !== df[name].length) { + throw new Error("All fields must be arrays of the same length"); + } + length = df[name].length; + } + var results = []; + var item; + for (var row = 0; row < length; row++) { + item = {}; + for (var col = 0; col < names.length; col++) { + item[names[col]] = df[names[col]][row]; + } + results.push(item); + } + return results; + }; + + window.HTMLWidgets.transposeArray2D = function(array) { + if (array.length === 0) return array; + var newArray = array[0].map(function(col, i) { + return array.map(function(row) { + return row[i] + }) + }); + return newArray; + }; + // Split value at splitChar, but allow splitChar to be escaped + // using escapeChar. Any other characters escaped by escapeChar + // will be included as usual (including escapeChar itself). + function splitWithEscape(value, splitChar, escapeChar) { + var results = []; + var escapeMode = false; + var currentResult = ""; + for (var pos = 0; pos < value.length; pos++) { + if (!escapeMode) { + if (value[pos] === splitChar) { + results.push(currentResult); + currentResult = ""; + } else if (value[pos] === escapeChar) { + escapeMode = true; + } else { + currentResult += value[pos]; + } + } else { + currentResult += value[pos]; + escapeMode = false; + } + } + if (currentResult !== "") { + results.push(currentResult); + } + return results; + } + // Function authored by Yihui/JJ Allaire + window.HTMLWidgets.evaluateStringMember = function(o, member) { + var parts = splitWithEscape(member, '.', '\\'); + for (var i = 0, l = parts.length; i < l; i++) { + var part = parts[i]; + // part may be a character or 'numeric' member name + if (o !== null && typeof o === "object" && part in o) { + if (i == (l - 1)) { // if we are at the end of the line then evalulate + if (typeof o[part] === "string") + o[part] = tryEval(o[part]); + } else { // otherwise continue to next embedded object + o = o[part]; + } + } + } + }; + + // Retrieve the HTMLWidget instance (i.e. the return value of an + // HTMLWidget binding's initialize() or factory() function) + // associated with an element, or null if none. + window.HTMLWidgets.getInstance = function(el) { + return elementData(el, "init_result"); + }; + + // Finds the first element in the scope that matches the selector, + // and returns the HTMLWidget instance (i.e. the return value of + // an HTMLWidget binding's initialize() or factory() function) + // associated with that element, if any. If no element matches the + // selector, or the first matching element has no HTMLWidget + // instance associated with it, then null is returned. + // + // The scope argument is optional, and defaults to window.document. + window.HTMLWidgets.find = function(scope, selector) { + if (arguments.length == 1) { + selector = scope; + scope = document; + } + + var el = scope.querySelector(selector); + if (el === null) { + return null; + } else { + return window.HTMLWidgets.getInstance(el); + } + }; + + // Finds all elements in the scope that match the selector, and + // returns the HTMLWidget instances (i.e. the return values of + // an HTMLWidget binding's initialize() or factory() function) + // associated with the elements, in an array. If elements that + // match the selector don't have an associated HTMLWidget + // instance, the returned array will contain nulls. + // + // The scope argument is optional, and defaults to window.document. + window.HTMLWidgets.findAll = function(scope, selector) { + if (arguments.length == 1) { + selector = scope; + scope = document; + } + + var nodes = scope.querySelectorAll(selector); + var results = []; + for (var i = 0; i < nodes.length; i++) { + results.push(window.HTMLWidgets.getInstance(nodes[i])); + } + return results; + }; + + var postRenderHandlers = []; + function invokePostRenderHandlers() { + while (postRenderHandlers.length) { + var handler = postRenderHandlers.shift(); + if (handler) { + handler(); + } + } + } + + // Register the given callback function to be invoked after the + // next time static widgets are rendered. + window.HTMLWidgets.addPostRenderHandler = function(callback) { + postRenderHandlers.push(callback); + }; + + // Takes a new-style instance-bound definition, and returns an + // old-style class-bound definition. This saves us from having + // to rewrite all the logic in this file to accomodate both + // types of definitions. + function createLegacyDefinitionAdapter(defn) { + var result = { + name: defn.name, + type: defn.type, + initialize: function(el, width, height) { + return defn.factory(el, width, height); + }, + renderValue: function(el, x, instance) { + return instance.renderValue(x); + }, + resize: function(el, width, height, instance) { + return instance.resize(width, height); + } + }; + + if (defn.find) + result.find = defn.find; + if (defn.renderError) + result.renderError = defn.renderError; + if (defn.clearError) + result.clearError = defn.clearError; + + return result; + } +})(); diff --git a/_posts/2023-12-14-comportamiento/comportamiento_files/plotly-binding-4.10.1/plotly.js b/_posts/2023-12-14-comportamiento/comportamiento_files/plotly-binding-4.10.1/plotly.js new file mode 100644 index 0000000..1155269 --- /dev/null +++ b/_posts/2023-12-14-comportamiento/comportamiento_files/plotly-binding-4.10.1/plotly.js @@ -0,0 +1,939 @@ + +HTMLWidgets.widget({ + name: "plotly", + type: "output", + + initialize: function(el, width, height) { + return {}; + }, + + resize: function(el, width, height, instance) { + if (instance.autosize) { + var width = instance.width || width; + var height = instance.height || height; + Plotly.relayout(el.id, {width: width, height: height}); + } + }, + + renderValue: function(el, x, instance) { + + // Plotly.relayout() mutates the plot input object, so make sure to + // keep a reference to the user-supplied width/height *before* + // we call Plotly.plot(); + var lay = x.layout || {}; + instance.width = lay.width; + instance.height = lay.height; + instance.autosize = lay.autosize || true; + + /* + / 'inform the world' about highlighting options this is so other + / crosstalk libraries have a chance to respond to special settings + / such as persistent selection. + / AFAIK, leaflet is the only library with such intergration + / https://github.com/rstudio/leaflet/pull/346/files#diff-ad0c2d51ce5fdf8c90c7395b102f4265R154 + */ + var ctConfig = crosstalk.var('plotlyCrosstalkOpts').set(x.highlight); + + if (typeof(window) !== "undefined") { + // make sure plots don't get created outside the network (for on-prem) + window.PLOTLYENV = window.PLOTLYENV || {}; + window.PLOTLYENV.BASE_URL = x.base_url; + + // Enable persistent selection when shift key is down + // https://stackoverflow.com/questions/1828613/check-if-a-key-is-down + var persistOnShift = function(e) { + if (!e) window.event; + if (e.shiftKey) { + x.highlight.persistent = true; + x.highlight.persistentShift = true; + } else { + x.highlight.persistent = false; + x.highlight.persistentShift = false; + } + }; + + // Only relevant if we haven't forced persistent mode at command line + if (!x.highlight.persistent) { + window.onmousemove = persistOnShift; + } + } + + var graphDiv = document.getElementById(el.id); + + // TODO: move the control panel injection strategy inside here... + HTMLWidgets.addPostRenderHandler(function() { + + // lower the z-index of the modebar to prevent it from highjacking hover + // (TODO: do this via CSS?) + // https://github.com/ropensci/plotly/issues/956 + // https://www.w3schools.com/jsref/prop_style_zindex.asp + var modebars = document.querySelectorAll(".js-plotly-plot .plotly .modebar"); + for (var i = 0; i < modebars.length; i++) { + modebars[i].style.zIndex = 1; + } + }); + + // inject a "control panel" holding selectize/dynamic color widget(s) + if ((x.selectize || x.highlight.dynamic) && !instance.plotly) { + var flex = document.createElement("div"); + flex.class = "plotly-crosstalk-control-panel"; + flex.style = "display: flex; flex-wrap: wrap"; + + // inject the colourpicker HTML container into the flexbox + if (x.highlight.dynamic) { + var pickerDiv = document.createElement("div"); + + var pickerInput = document.createElement("input"); + pickerInput.id = el.id + "-colourpicker"; + pickerInput.placeholder = "asdasd"; + + var pickerLabel = document.createElement("label"); + pickerLabel.for = pickerInput.id; + pickerLabel.innerHTML = "Brush color  "; + + pickerDiv.appendChild(pickerLabel); + pickerDiv.appendChild(pickerInput); + flex.appendChild(pickerDiv); + } + + // inject selectize HTML containers (one for every crosstalk group) + if (x.selectize) { + var ids = Object.keys(x.selectize); + + for (var i = 0; i < ids.length; i++) { + var container = document.createElement("div"); + container.id = ids[i]; + container.style = "width: 80%; height: 10%"; + container.class = "form-group crosstalk-input-plotly-highlight"; + + var label = document.createElement("label"); + label.for = ids[i]; + label.innerHTML = x.selectize[ids[i]].group; + label.class = "control-label"; + + var selectDiv = document.createElement("div"); + var select = document.createElement("select"); + select.multiple = true; + + selectDiv.appendChild(select); + container.appendChild(label); + container.appendChild(selectDiv); + flex.appendChild(container); + } + } + + // finally, insert the flexbox inside the htmlwidget container, + // but before the plotly graph div + graphDiv.parentElement.insertBefore(flex, graphDiv); + + if (x.highlight.dynamic) { + var picker = $("#" + pickerInput.id); + var colors = x.highlight.color || []; + // TODO: let users specify options? + var opts = { + value: colors[0], + showColour: "both", + palette: "limited", + allowedCols: colors.join(" "), + width: "20%", + height: "10%" + }; + picker.colourpicker({changeDelay: 0}); + picker.colourpicker("settings", opts); + picker.colourpicker("value", opts.value); + // inform crosstalk about a change in the current selection colour + var grps = x.highlight.ctGroups || []; + for (var i = 0; i < grps.length; i++) { + crosstalk.group(grps[i]).var('plotlySelectionColour') + .set(picker.colourpicker('value')); + } + picker.on("change", function() { + for (var i = 0; i < grps.length; i++) { + crosstalk.group(grps[i]).var('plotlySelectionColour') + .set(picker.colourpicker('value')); + } + }); + } + } + + // if no plot exists yet, create one with a particular configuration + if (!instance.plotly) { + + var plot = Plotly.newPlot(graphDiv, x); + instance.plotly = true; + + } else if (x.layout.transition) { + + var plot = Plotly.react(graphDiv, x); + + } else { + + // this is essentially equivalent to Plotly.newPlot(), but avoids creating + // a new webgl context + // https://github.com/plotly/plotly.js/blob/2b24f9def901831e61282076cf3f835598d56f0e/src/plot_api/plot_api.js#L531-L532 + + // TODO: restore crosstalk selections? + Plotly.purge(graphDiv); + // TODO: why is this necessary to get crosstalk working? + graphDiv.data = undefined; + graphDiv.layout = undefined; + var plot = Plotly.newPlot(graphDiv, x); + } + + // Trigger plotly.js calls defined via `plotlyProxy()` + plot.then(function() { + if (HTMLWidgets.shinyMode) { + Shiny.addCustomMessageHandler("plotly-calls", function(msg) { + var gd = document.getElementById(msg.id); + if (!gd) { + throw new Error("Couldn't find plotly graph with id: " + msg.id); + } + // This isn't an official plotly.js method, but it's the only current way to + // change just the configuration of a plot + // https://community.plot.ly/t/update-config-function/9057 + if (msg.method == "reconfig") { + Plotly.react(gd, gd.data, gd.layout, msg.args); + return; + } + if (!Plotly[msg.method]) { + throw new Error("Unknown method " + msg.method); + } + var args = [gd].concat(msg.args); + Plotly[msg.method].apply(null, args); + }); + } + + // plotly's mapbox API doesn't currently support setting bounding boxes + // https://www.mapbox.com/mapbox-gl-js/example/fitbounds/ + // so we do this manually... + // TODO: make sure this triggers on a redraw and relayout as well as on initial draw + var mapboxIDs = graphDiv._fullLayout._subplots.mapbox || []; + for (var i = 0; i < mapboxIDs.length; i++) { + var id = mapboxIDs[i]; + var mapOpts = x.layout[id] || {}; + var args = mapOpts._fitBounds || {}; + if (!args) { + continue; + } + var mapObj = graphDiv._fullLayout[id]._subplot.map; + mapObj.fitBounds(args.bounds, args.options); + } + + }); + + // Attach attributes (e.g., "key", "z") to plotly event data + function eventDataWithKey(eventData) { + if (eventData === undefined || !eventData.hasOwnProperty("points")) { + return null; + } + return eventData.points.map(function(pt) { + var obj = { + curveNumber: pt.curveNumber, + pointNumber: pt.pointNumber, + x: pt.x, + y: pt.y + }; + + // If 'z' is reported with the event data, then use it! + if (pt.hasOwnProperty("z")) { + obj.z = pt.z; + } + + if (pt.hasOwnProperty("customdata")) { + obj.customdata = pt.customdata; + } + + /* + TL;DR: (I think) we have to select the graph div (again) to attach keys... + + Why? Remember that crosstalk will dynamically add/delete traces + (see traceManager.prototype.updateSelection() below) + For this reason, we can't simply grab keys from x.data (like we did previously) + Moreover, we can't use _fullData, since that doesn't include + unofficial attributes. It's true that click/hover events fire with + pt.data, but drag events don't... + */ + var gd = document.getElementById(el.id); + var trace = gd.data[pt.curveNumber]; + + if (!trace._isSimpleKey) { + var attrsToAttach = ["key"]; + } else { + // simple keys fire the whole key + obj.key = trace.key; + var attrsToAttach = []; + } + + for (var i = 0; i < attrsToAttach.length; i++) { + var attr = trace[attrsToAttach[i]]; + if (Array.isArray(attr)) { + if (typeof pt.pointNumber === "number") { + obj[attrsToAttach[i]] = attr[pt.pointNumber]; + } else if (Array.isArray(pt.pointNumber)) { + obj[attrsToAttach[i]] = attr[pt.pointNumber[0]][pt.pointNumber[1]]; + } else if (Array.isArray(pt.pointNumbers)) { + obj[attrsToAttach[i]] = pt.pointNumbers.map(function(idx) { return attr[idx]; }); + } + } + } + return obj; + }); + } + + + var legendEventData = function(d) { + // if legendgroup is not relevant just return the trace + var trace = d.data[d.curveNumber]; + if (!trace.legendgroup) return trace; + + // if legendgroup was specified, return all traces that match the group + var legendgrps = d.data.map(function(trace){ return trace.legendgroup; }); + var traces = []; + for (i = 0; i < legendgrps.length; i++) { + if (legendgrps[i] == trace.legendgroup) { + traces.push(d.data[i]); + } + } + + return traces; + }; + + + // send user input event data to shiny + if (HTMLWidgets.shinyMode && Shiny.setInputValue) { + + // Some events clear other input values + // TODO: always register these? + var eventClearMap = { + plotly_deselect: ["plotly_selected", "plotly_selecting", "plotly_brushed", "plotly_brushing", "plotly_click"], + plotly_unhover: ["plotly_hover"], + plotly_doubleclick: ["plotly_click"] + }; + + Object.keys(eventClearMap).map(function(evt) { + graphDiv.on(evt, function() { + var inputsToClear = eventClearMap[evt]; + inputsToClear.map(function(input) { + Shiny.setInputValue(input + "-" + x.source, null, {priority: "event"}); + }); + }); + }); + + var eventDataFunctionMap = { + plotly_click: eventDataWithKey, + plotly_sunburstclick: eventDataWithKey, + plotly_hover: eventDataWithKey, + plotly_unhover: eventDataWithKey, + // If 'plotly_selected' has already been fired, and you click + // on the plot afterwards, this event fires `undefined`?!? + // That might be considered a plotly.js bug, but it doesn't make + // sense for this input change to occur if `d` is falsy because, + // even in the empty selection case, `d` is truthy (an object), + // and the 'plotly_deselect' event will reset this input + plotly_selected: function(d) { if (d) { return eventDataWithKey(d); } }, + plotly_selecting: function(d) { if (d) { return eventDataWithKey(d); } }, + plotly_brushed: function(d) { + if (d) { return d.range ? d.range : d.lassoPoints; } + }, + plotly_brushing: function(d) { + if (d) { return d.range ? d.range : d.lassoPoints; } + }, + plotly_legendclick: legendEventData, + plotly_legenddoubleclick: legendEventData, + plotly_clickannotation: function(d) { return d.fullAnnotation } + }; + + var registerShinyValue = function(event) { + var eventDataPreProcessor = eventDataFunctionMap[event] || function(d) { return d ? d : el.id }; + // some events are unique to the R package + var plotlyJSevent = (event == "plotly_brushed") ? "plotly_selected" : (event == "plotly_brushing") ? "plotly_selecting" : event; + // register the event + graphDiv.on(plotlyJSevent, function(d) { + Shiny.setInputValue( + event + "-" + x.source, + JSON.stringify(eventDataPreProcessor(d)), + {priority: "event"} + ); + }); + } + + var shinyEvents = x.shinyEvents || []; + shinyEvents.map(registerShinyValue); + } + + // Given an array of {curveNumber: x, pointNumber: y} objects, + // return a hash of { + // set1: {value: [key1, key2, ...], _isSimpleKey: false}, + // set2: {value: [key3, key4, ...], _isSimpleKey: false} + // } + function pointsToKeys(points) { + var keysBySet = {}; + for (var i = 0; i < points.length; i++) { + + var trace = graphDiv.data[points[i].curveNumber]; + if (!trace.key || !trace.set) { + continue; + } + + // set defaults for this keySet + // note that we don't track the nested property (yet) since we always + // emit the union -- http://cpsievert.github.io/talks/20161212b/#21 + keysBySet[trace.set] = keysBySet[trace.set] || { + value: [], + _isSimpleKey: trace._isSimpleKey + }; + + // Use pointNumber by default, but aggregated traces should emit pointNumbers + var ptNum = points[i].pointNumber; + var hasPtNum = typeof ptNum === "number"; + var ptNum = hasPtNum ? ptNum : points[i].pointNumbers; + + // selecting a point of a "simple" trace means: select the + // entire key attached to this trace, which is useful for, + // say clicking on a fitted line to select corresponding observations + var key = trace._isSimpleKey ? trace.key : Array.isArray(ptNum) ? ptNum.map(function(idx) { return trace.key[idx]; }) : trace.key[ptNum]; + // http://stackoverflow.com/questions/10865025/merge-flatten-an-array-of-arrays-in-javascript + var keyFlat = trace._isNestedKey ? [].concat.apply([], key) : key; + + // TODO: better to only add new values? + keysBySet[trace.set].value = keysBySet[trace.set].value.concat(keyFlat); + } + + return keysBySet; + } + + + x.highlight.color = x.highlight.color || []; + // make sure highlight color is an array + if (!Array.isArray(x.highlight.color)) { + x.highlight.color = [x.highlight.color]; + } + + var traceManager = new TraceManager(graphDiv, x.highlight); + + // Gather all *unique* sets. + var allSets = []; + for (var curveIdx = 0; curveIdx < x.data.length; curveIdx++) { + var newSet = x.data[curveIdx].set; + if (newSet) { + if (allSets.indexOf(newSet) === -1) { + allSets.push(newSet); + } + } + } + + // register event listeners for all sets + for (var i = 0; i < allSets.length; i++) { + + var set = allSets[i]; + var selection = new crosstalk.SelectionHandle(set); + var filter = new crosstalk.FilterHandle(set); + + var filterChange = function(e) { + removeBrush(el); + traceManager.updateFilter(set, e.value); + }; + filter.on("change", filterChange); + + + var selectionChange = function(e) { + + // Workaround for 'plotly_selected' now firing previously selected + // points (in addition to new ones) when holding shift key. In our case, + // we just want the new keys + if (x.highlight.on === "plotly_selected" && x.highlight.persistentShift) { + // https://stackoverflow.com/questions/1187518/how-to-get-the-difference-between-two-arrays-in-javascript + Array.prototype.diff = function(a) { + return this.filter(function(i) {return a.indexOf(i) < 0;}); + }; + e.value = e.value.diff(e.oldValue); + } + + // array of "event objects" tracking the selection history + // this is used to avoid adding redundant selections + var selectionHistory = crosstalk.var("plotlySelectionHistory").get() || []; + + // Construct an event object "defining" the current event. + var event = { + receiverID: traceManager.gd.id, + plotlySelectionColour: crosstalk.group(set).var("plotlySelectionColour").get() + }; + event[set] = e.value; + // TODO: is there a smarter way to check object equality? + if (selectionHistory.length > 0) { + var ev = JSON.stringify(event); + for (var i = 0; i < selectionHistory.length; i++) { + var sel = JSON.stringify(selectionHistory[i]); + if (sel == ev) { + return; + } + } + } + + // accumulate history for persistent selection + if (!x.highlight.persistent) { + selectionHistory = [event]; + } else { + selectionHistory.push(event); + } + crosstalk.var("plotlySelectionHistory").set(selectionHistory); + + // do the actual updating of traces, frames, and the selectize widget + traceManager.updateSelection(set, e.value); + // https://github.com/selectize/selectize.js/blob/master/docs/api.md#methods_items + if (x.selectize) { + if (!x.highlight.persistent || e.value === null) { + selectize.clear(true); + } + selectize.addItems(e.value, true); + selectize.close(); + } + } + selection.on("change", selectionChange); + + // Set a crosstalk variable selection value, triggering an update + var turnOn = function(e) { + if (e) { + var selectedKeys = pointsToKeys(e.points); + // Keys are group names, values are array of selected keys from group. + for (var set in selectedKeys) { + if (selectedKeys.hasOwnProperty(set)) { + selection.set(selectedKeys[set].value, {sender: el}); + } + } + } + }; + if (x.highlight.debounce > 0) { + turnOn = debounce(turnOn, x.highlight.debounce); + } + graphDiv.on(x.highlight.on, turnOn); + + graphDiv.on(x.highlight.off, function turnOff(e) { + // remove any visual clues + removeBrush(el); + // remove any selection history + crosstalk.var("plotlySelectionHistory").set(null); + // trigger the actual removal of selection traces + selection.set(null, {sender: el}); + }); + + // register a callback for selectize so that there is bi-directional + // communication between the widget and direct manipulation events + if (x.selectize) { + var selectizeID = Object.keys(x.selectize)[i]; + var items = x.selectize[selectizeID].items; + var first = [{value: "", label: "(All)"}]; + var opts = { + options: first.concat(items), + searchField: "label", + valueField: "value", + labelField: "label", + maxItems: 50 + }; + var select = $("#" + selectizeID).find("select")[0]; + var selectize = $(select).selectize(opts)[0].selectize; + // NOTE: this callback is triggered when *directly* altering + // dropdown items + selectize.on("change", function() { + var currentItems = traceManager.groupSelections[set] || []; + if (!x.highlight.persistent) { + removeBrush(el); + for (var i = 0; i < currentItems.length; i++) { + selectize.removeItem(currentItems[i], true); + } + } + var newItems = selectize.items.filter(function(idx) { + return currentItems.indexOf(idx) < 0; + }); + if (newItems.length > 0) { + traceManager.updateSelection(set, newItems); + } else { + // Item has been removed... + // TODO: this logic won't work for dynamically changing palette + traceManager.updateSelection(set, null); + traceManager.updateSelection(set, selectize.items); + } + }); + } + } // end of selectionChange + + } // end of renderValue +}); // end of widget definition + +/** + * @param graphDiv The Plotly graph div + * @param highlight An object with options for updating selection(s) + */ +function TraceManager(graphDiv, highlight) { + // The Plotly graph div + this.gd = graphDiv; + + // Preserve the original data. + // TODO: try using Lib.extendFlat() as done in + // https://github.com/plotly/plotly.js/pull/1136 + this.origData = JSON.parse(JSON.stringify(graphDiv.data)); + + // avoid doing this over and over + this.origOpacity = []; + for (var i = 0; i < this.origData.length; i++) { + this.origOpacity[i] = this.origData[i].opacity === 0 ? 0 : (this.origData[i].opacity || 1); + } + + // key: group name, value: null or array of keys representing the + // most recently received selection for that group. + this.groupSelections = {}; + + // selection parameters (e.g., transient versus persistent selection) + this.highlight = highlight; +} + +TraceManager.prototype.close = function() { + // TODO: Unhook all event handlers +}; + +TraceManager.prototype.updateFilter = function(group, keys) { + + if (typeof(keys) === "undefined" || keys === null) { + + this.gd.data = JSON.parse(JSON.stringify(this.origData)); + + } else { + + var traces = []; + for (var i = 0; i < this.origData.length; i++) { + var trace = this.origData[i]; + if (!trace.key || trace.set !== group) { + continue; + } + var matchFunc = getMatchFunc(trace); + var matches = matchFunc(trace.key, keys); + + if (matches.length > 0) { + if (!trace._isSimpleKey) { + // subsetArrayAttrs doesn't mutate trace (it makes a modified clone) + trace = subsetArrayAttrs(trace, matches); + } + traces.push(trace); + } + } + this.gd.data = traces; + } + + Plotly.redraw(this.gd); + + // NOTE: we purposely do _not_ restore selection(s), since on filter, + // axis likely will update, changing the pixel -> data mapping, leading + // to a likely mismatch in the brush outline and highlighted marks + +}; + +TraceManager.prototype.updateSelection = function(group, keys) { + + if (keys !== null && !Array.isArray(keys)) { + throw new Error("Invalid keys argument; null or array expected"); + } + + // if selection has been cleared, or if this is transient + // selection, delete the "selection traces" + var nNewTraces = this.gd.data.length - this.origData.length; + if (keys === null || !this.highlight.persistent && nNewTraces > 0) { + var tracesToRemove = []; + for (var i = 0; i < this.gd.data.length; i++) { + if (this.gd.data[i]._isCrosstalkTrace) tracesToRemove.push(i); + } + Plotly.deleteTraces(this.gd, tracesToRemove); + this.groupSelections[group] = keys; + } else { + // add to the groupSelection, rather than overwriting it + // TODO: can this be removed? + this.groupSelections[group] = this.groupSelections[group] || []; + for (var i = 0; i < keys.length; i++) { + var k = keys[i]; + if (this.groupSelections[group].indexOf(k) < 0) { + this.groupSelections[group].push(k); + } + } + } + + if (keys === null) { + + Plotly.restyle(this.gd, {"opacity": this.origOpacity}); + + } else if (keys.length >= 1) { + + // placeholder for new "selection traces" + var traces = []; + // this variable is set in R/highlight.R + var selectionColour = crosstalk.group(group).var("plotlySelectionColour").get() || + this.highlight.color[0]; + + for (var i = 0; i < this.origData.length; i++) { + // TODO: try using Lib.extendFlat() as done in + // https://github.com/plotly/plotly.js/pull/1136 + var trace = JSON.parse(JSON.stringify(this.gd.data[i])); + if (!trace.key || trace.set !== group) { + continue; + } + // Get sorted array of matching indices in trace.key + var matchFunc = getMatchFunc(trace); + var matches = matchFunc(trace.key, keys); + + if (matches.length > 0) { + // If this is a "simple" key, that means select the entire trace + if (!trace._isSimpleKey) { + trace = subsetArrayAttrs(trace, matches); + } + // reach into the full trace object so we can properly reflect the + // selection attributes in every view + var d = this.gd._fullData[i]; + + /* + / Recursively inherit selection attributes from various sources, + / in order of preference: + / (1) official plotly.js selected attribute + / (2) highlight(selected = attrs_selected(...)) + */ + // TODO: it would be neat to have a dropdown to dynamically specify these! + $.extend(true, trace, this.highlight.selected); + + // if it is defined, override color with the "dynamic brush color"" + if (d.marker) { + trace.marker = trace.marker || {}; + trace.marker.color = selectionColour || trace.marker.color || d.marker.color; + } + if (d.line) { + trace.line = trace.line || {}; + trace.line.color = selectionColour || trace.line.color || d.line.color; + } + if (d.textfont) { + trace.textfont = trace.textfont || {}; + trace.textfont.color = selectionColour || trace.textfont.color || d.textfont.color; + } + if (d.fillcolor) { + // TODO: should selectionColour inherit alpha from the existing fillcolor? + trace.fillcolor = selectionColour || trace.fillcolor || d.fillcolor; + } + // attach a sensible name/legendgroup + trace.name = trace.name || keys.join("
"); + trace.legendgroup = trace.legendgroup || keys.join("
"); + + // keep track of mapping between this new trace and the trace it targets + // (necessary for updating frames to reflect the selection traces) + trace._originalIndex = i; + trace._newIndex = this.gd._fullData.length + traces.length; + trace._isCrosstalkTrace = true; + traces.push(trace); + } + } + + if (traces.length > 0) { + + Plotly.addTraces(this.gd, traces).then(function(gd) { + // incrementally add selection traces to frames + // (this is heavily inspired by Plotly.Plots.modifyFrames() + // in src/plots/plots.js) + var _hash = gd._transitionData._frameHash; + var _frames = gd._transitionData._frames || []; + + for (var i = 0; i < _frames.length; i++) { + + // add to _frames[i].traces *if* this frame references selected trace(s) + var newIndices = []; + for (var j = 0; j < traces.length; j++) { + var tr = traces[j]; + if (_frames[i].traces.indexOf(tr._originalIndex) > -1) { + newIndices.push(tr._newIndex); + _frames[i].traces.push(tr._newIndex); + } + } + + // nothing to do... + if (newIndices.length === 0) { + continue; + } + + var ctr = 0; + var nFrameTraces = _frames[i].data.length; + + for (var j = 0; j < nFrameTraces; j++) { + var frameTrace = _frames[i].data[j]; + if (!frameTrace.key || frameTrace.set !== group) { + continue; + } + + var matchFunc = getMatchFunc(frameTrace); + var matches = matchFunc(frameTrace.key, keys); + + if (matches.length > 0) { + if (!trace._isSimpleKey) { + frameTrace = subsetArrayAttrs(frameTrace, matches); + } + var d = gd._fullData[newIndices[ctr]]; + if (d.marker) { + frameTrace.marker = d.marker; + } + if (d.line) { + frameTrace.line = d.line; + } + if (d.textfont) { + frameTrace.textfont = d.textfont; + } + ctr = ctr + 1; + _frames[i].data.push(frameTrace); + } + } + + // update gd._transitionData._frameHash + _hash[_frames[i].name] = _frames[i]; + } + + }); + + // dim traces that have a set matching the set of selection sets + var tracesToDim = [], + opacities = [], + sets = Object.keys(this.groupSelections), + n = this.origData.length; + + for (var i = 0; i < n; i++) { + var opacity = this.origOpacity[i] || 1; + // have we already dimmed this trace? Or is this even worth doing? + if (opacity !== this.gd._fullData[i].opacity || this.highlight.opacityDim === 1) { + continue; + } + // is this set an element of the set of selection sets? + var matches = findMatches(sets, [this.gd.data[i].set]); + if (matches.length) { + tracesToDim.push(i); + opacities.push(opacity * this.highlight.opacityDim); + } + } + + if (tracesToDim.length > 0) { + Plotly.restyle(this.gd, {"opacity": opacities}, tracesToDim); + // turn off the selected/unselected API + Plotly.restyle(this.gd, {"selectedpoints": null}); + } + + } + + } +}; + +/* +Note: in all of these match functions, we assume needleSet (i.e. the selected keys) +is a 1D (or flat) array. The real difference is the meaning of haystack. +findMatches() does the usual thing you'd expect for +linked brushing on a scatterplot matrix. findSimpleMatches() returns a match iff +haystack is a subset of the needleSet. findNestedMatches() returns +*/ + +function getMatchFunc(trace) { + return (trace._isNestedKey) ? findNestedMatches : + (trace._isSimpleKey) ? findSimpleMatches : findMatches; +} + +// find matches for "flat" keys +function findMatches(haystack, needleSet) { + var matches = []; + haystack.forEach(function(obj, i) { + if (obj === null || needleSet.indexOf(obj) >= 0) { + matches.push(i); + } + }); + return matches; +} + +// find matches for "simple" keys +function findSimpleMatches(haystack, needleSet) { + var match = haystack.every(function(val) { + return val === null || needleSet.indexOf(val) >= 0; + }); + // yes, this doesn't make much sense other than conforming + // to the output type of the other match functions + return (match) ? [0] : [] +} + +// find matches for a "nested" haystack (2D arrays) +function findNestedMatches(haystack, needleSet) { + var matches = []; + for (var i = 0; i < haystack.length; i++) { + var hay = haystack[i]; + var match = hay.every(function(val) { + return val === null || needleSet.indexOf(val) >= 0; + }); + if (match) { + matches.push(i); + } + } + return matches; +} + +function isPlainObject(obj) { + return ( + Object.prototype.toString.call(obj) === '[object Object]' && + Object.getPrototypeOf(obj) === Object.prototype + ); +} + +function subsetArrayAttrs(obj, indices) { + var newObj = {}; + Object.keys(obj).forEach(function(k) { + var val = obj[k]; + + if (k.charAt(0) === "_") { + newObj[k] = val; + } else if (k === "transforms" && Array.isArray(val)) { + newObj[k] = val.map(function(transform) { + return subsetArrayAttrs(transform, indices); + }); + } else if (k === "colorscale" && Array.isArray(val)) { + newObj[k] = val; + } else if (isPlainObject(val)) { + newObj[k] = subsetArrayAttrs(val, indices); + } else if (Array.isArray(val)) { + newObj[k] = subsetArray(val, indices); + } else { + newObj[k] = val; + } + }); + return newObj; +} + +function subsetArray(arr, indices) { + var result = []; + for (var i = 0; i < indices.length; i++) { + result.push(arr[indices[i]]); + } + return result; +} + +// Convenience function for removing plotly's brush +function removeBrush(el) { + var outlines = el.querySelectorAll(".select-outline"); + for (var i = 0; i < outlines.length; i++) { + outlines[i].remove(); + } +} + + +// https://davidwalsh.name/javascript-debounce-function + +// Returns a function, that, as long as it continues to be invoked, will not +// be triggered. The function will be called after it stops being called for +// N milliseconds. If `immediate` is passed, trigger the function on the +// leading edge, instead of the trailing. +function debounce(func, wait, immediate) { + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; +}; diff --git a/_posts/2023-12-14-comportamiento/graf_tabl.R b/_posts/2023-12-14-comportamiento/graf_tabl.R index 69f20ee..de3e4eb 100644 --- a/_posts/2023-12-14-comportamiento/graf_tabl.R +++ b/_posts/2023-12-14-comportamiento/graf_tabl.R @@ -52,7 +52,6 @@ t1<-b_evyth_ct %>% #tabla general #plotly -rm(g1) g1<- t1%>% filter(al_menos_un_viaje == "Si") %>% plot_ly( diff --git a/_posts/2023-12-14-comportamiento/imag1.PNG b/_posts/2023-12-14-comportamiento/imag1.PNG new file mode 100644 index 0000000000000000000000000000000000000000..906579431d5fc2627861962c798b6ff39d417204 GIT binary patch literal 24231 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 H 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 literal 0 HcmV?d00001 diff --git a/docs/index.html b/docs/index.html index dbe6829..4cddb90 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2252,1050 +2252,1050 @@

${suggestion.title}

- - - - - - - -
-
-

Novedades e Investigaciones

- - - -
- -
-
-

Comportamiento turístico de las personas, años 2021-2022

-
-

-
-
- - - -
- -
-
-

Nueva versión del Tablero de Turismo Internacional

-
-

Gasto turístico y serie histórica completa

-
-
- - - -
- -
-
-

Conectividad Terrestre Interurbana

-
-

Nueva dimensión de análisis del turismo en Argentina

-
-
- - - -
- -
-
-

La DNMyE publica su memoria de gestión

-
-

Se presenta el documento que describe las principales tareas de la Dirección, los productos que elabora y las mejoras introducidas en los últimos años. Se incluye, además, el Manual de procedimientos de la DNMyE.

-
-
- - - -
- -
-
-

Índice Turístico de Localidades

-
-

Una propuesta metodológica para la medición de la actividad turística en localidades de Argentina

-
-
- - - -
- -
-
-

Nuevo tablero: últimos datos del turismo en Argentina

-
-

El nuevo tablero presenta de manera resumida los principales indicadores publicados en el SINTA e indicadores seleccionados de otros organismos, que dan a conocer los resultados más recientes del turismo en Argentina y a nivel mundial.

-
-
- - - -
- -
-
-

Actualización de la Cuenta Satélite de Turismo de Argentina (CST-A)

-
-

Publicación de la estimación definitiva de los indicadores de 2019 y 2020, junto con la estimación provisoria de 2021 y 2022.

-
-
- - - -
- -
-
-

Turismo de naturaleza: actualización de indicadores

-
-

Nueva versión del informe que caracteriza a los viajeros residentes y no residentes que realizaron actividades de naturaleza en Argentina

-
-
- - - -
- -
-
-

Áreas Protegidas

-
-

Publicación del Informe de visitas en Áreas Protegidas en Argentina, que recopila información de visitas en Parques Nacionales y Provinciales del país.

-
-
- - - -
- -
-
-

La DNMyE publica el Anuario Estadístico de Turismo 2022

-
-

En línea con la reanudación de la publicación del informe anual estadístico del turismo (AET), iniciada con el AET 2021, aquí se presenta el AET 2022, que contiene las estadísticas que muestran la recuperación del sector tras la profunda crisis atravesada por la pandemia COVID-19.

-
-
- - - -
- -
-
-

Crucerismo Marítimo en la Argentina

-
-

Publicación del Informe de Crucerismo Marítimo en la Argentina con información de la última temporada.

-
-
- - - -
- -
-
-

Actualización de los datos abiertos de Previaje

-
-

Publicación de los recursos correspondientes a la cuarta edición del programa, que tuvo vigencia durante fines de mayo y junio 2023

-
-
- - - -
- -
-
-

Datos abiertos de Conectividad Aérea

-
-

Publicación de datos abiertos sobre transporte aerocomercial en Argentina, con información sobre vuelos, pasajeros y asientos.

-
-
- - - -
- -
-
-

El turismo en los Objetivos de Desarrollo Sostenible

-
-

La DNMyE publica un documento de trabajo que describe el aporte del turismo -al cumplimiento de los Objetivos de Desarrollo Sostenible, acompañado de un tablero de seguimiento de los -indicadores propuestos para evaluar su desempeño

-
-
- - - -
- -
-
-

La DNMyE retoma la publicación de los Anuarios Estadísticos de Turismo

-
-

Con la publicación del Anuario 2021 se reanuda el trabajo de compilación de estadísticas del turismo que se realizó entre 2005 y 2015. Esta nueva edición presenta características novedosas en términos de formato, reproducibilidad y contenido.

-
-
- - - -
- -
-
-

Lanzamiento de la Diplomatura en Generación y Análisis de Información en Turismo

-
-

Iniciativa del Ministerio de Turismo y Deportes de la Nación y la Universidad Nacional de San Martín (UNSAM)

-
-
- - - -
- -
-
-

Conectividad Aérea

-
-

Publicación del Informe y Reporte de Conectividad Aérea en la Argentina, que recopila información turística del mercado aerocomercial del país.

-
-
- - - -
- -
-
-

Big data para el turismo

-
-

El uso de fuentes alternativas para una mejor comprensión del sector turístico en Argentina. El turismo receptivo en #LaRutaNatural

-
-
- - - -
- -
-
-

Puesta en marcha del Sistema Federal de Gestión de Datos Turísticos

-
-

Puesta en producción de `<data>TUR`, la una nueva plataforma web para el intercambio y la gestión de datos del turismo entre organismos.

-
-
- - - -
- -
-
-

Tablero crucerismo marítimo en argentina

-
-

Herramienta interactiva para navegar las estadísticas principales sobre crucerismo marítimo en nuestro país

-
-
- - - -
- -
-
-

PUNA: actualización de indicadores y nuevos lineamientos de trabajo

-
-

Publicación de información actualizada sobre alojamiento colectivo al 2021 y novedades en el análisis y gestión de los datos.

-
-
- - - -
- -
-
-

Turismo en Argentina Productiva 2030

-
-

Publicación del documento elaborado por el Ministerio de Turismo y Deportes - Misión 11: Potenciar la actividad turística para el desarrollo territorial sustentable a partir de la gran biodiversidad local

-
-
- - - -
- -
-
-

Crucerismo Marítimo en la Argentina

-
-

Publicación del Informe de Crucerismo Marítimo en la Argentina, que recopila información de cruceristas y cruceros en los puertos de nuestro país.

-
-
- - - -
- -
-
-

Nivel de Desarrollo Turístico de las localidades de Argentina

-
-

Publicación del Documento de Trabajo N° 8 que avanza en un primer ejercicio de clasificación de las localidades según nivel de desarrollo turístico.

-
-
- - - -
- -
-
-

Publicación de datos abiertos de Previaje

-
-

Publicación de los primeros datos que surgen de la carga de viajes y comprobantes y del gasto del crédito de las primeras tres ediciones del programa.

-
-
- - - -
- -
-
-

Encuesta a prestadores turísticos

-
-

Publicación del documento que resume los resultados de la encuesta a prestadores turísticos que participaron de programas del Ministerio, realizada en marzo de 2022.

-
-
- - - -
- -
-
-

Nueva versión del Tablero de Turismo Internacional

-
-

Nuevos datos para la caracterización de los visitantes

-
-
- - - -
- -
-
-

Mapa Interactivo para Inversiones Turísticas

-
-

Desarrollo de un visor de información geográfica sobre el turismo junto a oportunidades de negocio geolocalizadas para la toma de decisiones de inversión.

-
-
- - - -
- -
-
-

Datos abiertos: nuevo dataset sobre Turismo Social

-
-

Publicación de los primeros datos abiertos sobre Turismo Social en Argentina, con información de turistas que visitaron las Unidades Turísticas.

-
-
- - - -
- -
-
-

Estacionalidad en el turismo

-
-

Caracterización de la estacionalidad turística en Argentina a partir de un análisis territorial de la ocupación hotelera y el empleo.

-
-
- - - -
- -
-
-

SintIA II: un bot como asistente interno

-
-

¿Cómo un bot puede ayudar en el flujo de trabajo de una dirección de estadísticas?

-
-
- - - -
- -
-
-

Nueva versión del Monitor de provincias

-
-

Publicamos una versión renovada del portal de estadísticas del turismo por provincia.

-
-
- - - -
- -
-
-

Novedades en la Enucesta de Turismo Internacional (ETI)

-
-

Resultado de trabajo conjunto con el Instituto Nacional de Estadística y Censos (INDEC)

-
-
- - - -
- -
-
-

Tablero de conectividad aérea

-
-

Nuevo tablero interactivo de datos aeromerciales de Argentina

-
-
- - - -
- -
-
-

El turismo de Argentina en LatinR

-
-

Participación del equipo de trabajo de la Dirección Nacional de Mercados y Estadística (DNMYE) en el V Congreso Latinoamericano sobre Uso de R en Investigación + Desarrollo

-
-
- - - -
- -
-
-

SintIA: el robot del SINTA

-
-

Nueva herramienta para mantenerse actualizado con los últimos datos del Sistema de Información Turística de la Argentina (SINTA)

-
-
- - - -
- -
-
-

Directrices de Gestión Ambiental

-
-

Principales indicadores para caracterizar las organizaciones distinguidas por el programa de Directrices de Gestión Ambiental del Sistema Argentino de Calidad Turística.

-
-
- - - -
- -
-
-

Datos abiertos de turismo

-
-

Para consultar, descargar, usar, compartir.

-
-
- - - -
- -
-
-

Agencias de Viajes y de Turismo Estudiantil

-
-

Caracterización del sector en base al Registro de Agencias de Viajes de la Dirección Nacional de Agencias de Viajes (datos a marzo de 2022)

-
-
- - - -
- -
-
-

BIBLIOTECA

-
-

Este nuevo micrositio del Sistema de Información Turística de la Argentina (SINTA) reune investigaciones, documentos de trabajo, metodológicos y de coyuntura del sector turístico producidos por la DNMyE

-
-
- - - -
- -
-
-

Publicación del Informe Mensual de Estadísticas de Turismo

-
-

Publicación regular de la Dirección Nacional de Mercados y Estadística que compendia la última información disponible del turismo en Argentina y el mundo

-
-
- - - -
- -
-
-

{herramientas} para el procesamiento de datos y {comunicacion} de la DNMyE

-
-

Presentación de dos librerías con funciones del paquete estadístico R, desarrolladas por la Dirección Nacional de Mercados y Estadística del Ministerio de Turismo y Deportes de la Nación

-
-
- - - -
- -
-
-

MapeAr: una herramienta para visualizar información geográfica

-
-

¿Cómo convertir una base con datos georeferenciados en un mapa de Argentina?

-
-
- - - -
- -
-
-

Fuentes de datos: Padrón Único Nacional de Alojamiento (PUNA)

-
-

Siguiendo con la política de apertura de datos, la Dirección Nacional de Mercados y Estadística pone a disposición principales indicadores y la base de datos agregada de alojamientos colectivos 2020 del PUNA.

-
-
- - - -
- -
-
-

Fuentes de datos: Encuesta de Viajes y Turismo de los Hogares (EVyTH)

-
-

Características principales del relevamiento y potenciales usos de la información generada.

-
-
- - - -
- -
-
-

Fuentes de datos: Estimación Nacional del Turismo Internacional y Encuesta de Turismo Internacional (ETI)

-
-

Características principales del relevamiento y potenciales usos de la información generada.

-
-
- - - -
- -
-
-

Fuentes de datos: Encuesta de Ocupación Hotelera (EOH)

-
-

Características principales del relevamiento y potenciales usos de la información generada.

-
-
- - - -
- -
-
-

Publicación de microdatos de la Encuesta de Viajes y Turismo de los Hogares (EVyTH) - Serie 2012-2021

-
-

La Dirección Nacional de Mercados y Estadísticas pone a disposición la base de microdatos de la EVyTH-MINTURDEP con información desde los inicios del operativo hasta el último trimestre disponible.

-
-
- - - -
- -
-
-

CST-A PARTE II

-
-

Recorrido por las Tablas de la Cuenta Satélite de Turismo de Argentina (CST -A) identificando qué información nos ofrecen y cómo se interrelacionan para obtener el impacto económico del turismo

-
-
- - - -
- -
-
-

Turismo de Cultura

-
-

Principales indicadores para caracterizar los viajes y el perfil de las y los viajeros, nacionales y extranjeros, que realizan Turismo de Cultura en Argentina.

-
-
- - - -
- -
-
-

Turismo de Naturaleza

-
-

Principales indicadores para caracterizar los viajes y el perfil de las y los viajeros, nacionales y extranjeros, que realizan Turismo de Naturaleza en Argentina.

-
-
- - - -
- -
-
-

Tutorial SINTA

-
-

Un recorrido por el Sistema de Información Turística de la Argentina.

-
-
- - - -
- -
-
-

Monitor Estadístico de las Provincias

-
-

Compendio de los principales indicadores del turismo a nivel provincial

-
-
- - - -
- -
-
-

Publicación del Calendario de Difusión

-
-

Siguiendo con la política de apertura de información estadística, se pone a disposición el calendario de publicaciones de la Dirección de Mercados y Estadísticas, desde donde se podrá acceder a los productos estadísticos del área.

-
-
- - - -
- -
-
-

Publicación de la Cuenta Satélite de Turismo de la Argentina

-
-

La Cuenta Satélite de Turismo de la Argentina (CST-A) proporciona el marco para la medición de la contribución del sector turístico a la economía

-
-
- - - -
- -
-
-

Actualización de microdatos de la Encuesta de Viajes y Turismo de los Hogares (EVyTH)

-
-

Siguiendo con la política de apertura de datos, la Dirección Nacional de Mercados y Estadística (DNMyE) actualiza la base de microdatos de la EVyTH.

-
-
- - - -
- -
-
-

Empleo registrado en Turismo

-
-

Puestos de trabajo, empleo por género y empresas en ramas turísticas

-
-
- - - -
- -
-
-

Turismo en el Mercado Único y Libre de Cambios (MULC)

-
-

Ingresos y egresos de divisas vinculados al turismo en el mercado oficial de cambios.

-
-
- - - -
- -
-
-

Publicación de microdatos de la Encuesta de Viajes y Turismo de los Hogares (EVyTH)

-
-

Siguiendo con la política de apertura de datos, la Dirección Nacional de Mercados y Estadísticas pone a disposición la base de microdatos de la EVyTH.

-
-
- - - -
- -
-
-

Te damos la bienvenida a la Bitácora de Turismo

-
-

Un espacio virtual de la Dirección Nacional de Mercados y Estadística (DNMyE) en el que nos proponemos compartir avances de investigaciones, datos y reportes relacionados con el desarrollo de la industria turística de Argentina.

-
-
-
- -
- + + + + + + + +
+
+

Novedades e Investigaciones

+ + + +
+ +
+
+

Comportamiento turístico de las personas, años 2021-2022

+
+

Publicación de informes anuales con estimaciones sobre comportamiento turístico de las personas en base a la Encuesta de Viajes y Turismo de los Hogares (EVyTH)

+
+
+ + + +
+ +
+
+

Nueva versión del Tablero de Turismo Internacional

+
+

Gasto turístico y serie histórica completa

+
+
+ + + +
+ +
+
+

Conectividad Terrestre Interurbana

+
+

Nueva dimensión de análisis del turismo en Argentina

+
+
+ + + +
+ +
+
+

La DNMyE publica su memoria de gestión

+
+

Se presenta el documento que describe las principales tareas de la Dirección, los productos que elabora y las mejoras introducidas en los últimos años. Se incluye, además, el Manual de procedimientos de la DNMyE.

+
+
+ + + +
+ +
+
+

Índice Turístico de Localidades

+
+

Una propuesta metodológica para la medición de la actividad turística en localidades de Argentina

+
+
+ + + +
+ +
+
+

Nuevo tablero: últimos datos del turismo en Argentina

+
+

El nuevo tablero presenta de manera resumida los principales indicadores publicados en el SINTA e indicadores seleccionados de otros organismos, que dan a conocer los resultados más recientes del turismo en Argentina y a nivel mundial.

+
+
+ + + +
+ +
+
+

Actualización de la Cuenta Satélite de Turismo de Argentina (CST-A)

+
+

Publicación de la estimación definitiva de los indicadores de 2019 y 2020, junto con la estimación provisoria de 2021 y 2022.

+
+
+ + + +
+ +
+
+

Turismo de naturaleza: actualización de indicadores

+
+

Nueva versión del informe que caracteriza a los viajeros residentes y no residentes que realizaron actividades de naturaleza en Argentina

+
+
+ + + +
+ +
+
+

Áreas Protegidas

+
+

Publicación del Informe de visitas en Áreas Protegidas en Argentina, que recopila información de visitas en Parques Nacionales y Provinciales del país.

+
+
+ + + +
+ +
+
+

La DNMyE publica el Anuario Estadístico de Turismo 2022

+
+

En línea con la reanudación de la publicación del informe anual estadístico del turismo (AET), iniciada con el AET 2021, aquí se presenta el AET 2022, que contiene las estadísticas que muestran la recuperación del sector tras la profunda crisis atravesada por la pandemia COVID-19.

+
+
+ + + +
+ +
+
+

Crucerismo Marítimo en la Argentina

+
+

Publicación del Informe de Crucerismo Marítimo en la Argentina con información de la última temporada.

+
+
+ + + +
+ +
+
+

Actualización de los datos abiertos de Previaje

+
+

Publicación de los recursos correspondientes a la cuarta edición del programa, que tuvo vigencia durante fines de mayo y junio 2023

+
+
+ + + +
+ +
+
+

Datos abiertos de Conectividad Aérea

+
+

Publicación de datos abiertos sobre transporte aerocomercial en Argentina, con información sobre vuelos, pasajeros y asientos.

+
+
+ + + +
+ +
+
+

El turismo en los Objetivos de Desarrollo Sostenible

+
+

La DNMyE publica un documento de trabajo que describe el aporte del turismo +al cumplimiento de los Objetivos de Desarrollo Sostenible, acompañado de un tablero de seguimiento de los +indicadores propuestos para evaluar su desempeño

+
+
+ + + +
+ +
+
+

La DNMyE retoma la publicación de los Anuarios Estadísticos de Turismo

+
+

Con la publicación del Anuario 2021 se reanuda el trabajo de compilación de estadísticas del turismo que se realizó entre 2005 y 2015. Esta nueva edición presenta características novedosas en términos de formato, reproducibilidad y contenido.

+
+
+ + + +
+ +
+
+

Lanzamiento de la Diplomatura en Generación y Análisis de Información en Turismo

+
+

Iniciativa del Ministerio de Turismo y Deportes de la Nación y la Universidad Nacional de San Martín (UNSAM)

+
+
+ + + +
+ +
+
+

Conectividad Aérea

+
+

Publicación del Informe y Reporte de Conectividad Aérea en la Argentina, que recopila información turística del mercado aerocomercial del país.

+
+
+ + + +
+ +
+
+

Big data para el turismo

+
+

El uso de fuentes alternativas para una mejor comprensión del sector turístico en Argentina. El turismo receptivo en #LaRutaNatural

+
+
+ + + +
+ +
+
+

Puesta en marcha del Sistema Federal de Gestión de Datos Turísticos

+
+

Puesta en producción de `<data>TUR`, la una nueva plataforma web para el intercambio y la gestión de datos del turismo entre organismos.

+
+
+ + + +
+ +
+
+

Tablero crucerismo marítimo en argentina

+
+

Herramienta interactiva para navegar las estadísticas principales sobre crucerismo marítimo en nuestro país

+
+
+ + + +
+ +
+
+

PUNA: actualización de indicadores y nuevos lineamientos de trabajo

+
+

Publicación de información actualizada sobre alojamiento colectivo al 2021 y novedades en el análisis y gestión de los datos.

+
+
+ + + +
+ +
+
+

Turismo en Argentina Productiva 2030

+
+

Publicación del documento elaborado por el Ministerio de Turismo y Deportes - Misión 11: Potenciar la actividad turística para el desarrollo territorial sustentable a partir de la gran biodiversidad local

+
+
+ + + +
+ +
+
+

Crucerismo Marítimo en la Argentina

+
+

Publicación del Informe de Crucerismo Marítimo en la Argentina, que recopila información de cruceristas y cruceros en los puertos de nuestro país.

+
+
+ + + +
+ +
+
+

Nivel de Desarrollo Turístico de las localidades de Argentina

+
+

Publicación del Documento de Trabajo N° 8 que avanza en un primer ejercicio de clasificación de las localidades según nivel de desarrollo turístico.

+
+
+ + + +
+ +
+
+

Publicación de datos abiertos de Previaje

+
+

Publicación de los primeros datos que surgen de la carga de viajes y comprobantes y del gasto del crédito de las primeras tres ediciones del programa.

+
+
+ + + +
+ +
+
+

Encuesta a prestadores turísticos

+
+

Publicación del documento que resume los resultados de la encuesta a prestadores turísticos que participaron de programas del Ministerio, realizada en marzo de 2022.

+
+
+ + + +
+ +
+
+

Nueva versión del Tablero de Turismo Internacional

+
+

Nuevos datos para la caracterización de los visitantes

+
+
+ + + +
+ +
+
+

Mapa Interactivo para Inversiones Turísticas

+
+

Desarrollo de un visor de información geográfica sobre el turismo junto a oportunidades de negocio geolocalizadas para la toma de decisiones de inversión.

+
+
+ + + +
+ +
+
+

Datos abiertos: nuevo dataset sobre Turismo Social

+
+

Publicación de los primeros datos abiertos sobre Turismo Social en Argentina, con información de turistas que visitaron las Unidades Turísticas.

+
+
+ + + +
+ +
+
+

Estacionalidad en el turismo

+
+

Caracterización de la estacionalidad turística en Argentina a partir de un análisis territorial de la ocupación hotelera y el empleo.

+
+
+ + + +
+ +
+
+

SintIA II: un bot como asistente interno

+
+

¿Cómo un bot puede ayudar en el flujo de trabajo de una dirección de estadísticas?

+
+
+ + + +
+ +
+
+

Nueva versión del Monitor de provincias

+
+

Publicamos una versión renovada del portal de estadísticas del turismo por provincia.

+
+
+ + + +
+ +
+
+

Novedades en la Enucesta de Turismo Internacional (ETI)

+
+

Resultado de trabajo conjunto con el Instituto Nacional de Estadística y Censos (INDEC)

+
+
+ + + +
+ +
+
+

Tablero de conectividad aérea

+
+

Nuevo tablero interactivo de datos aeromerciales de Argentina

+
+
+ + + +
+ +
+
+

El turismo de Argentina en LatinR

+
+

Participación del equipo de trabajo de la Dirección Nacional de Mercados y Estadística (DNMYE) en el V Congreso Latinoamericano sobre Uso de R en Investigación + Desarrollo

+
+
+ + + +
+ +
+
+

SintIA: el robot del SINTA

+
+

Nueva herramienta para mantenerse actualizado con los últimos datos del Sistema de Información Turística de la Argentina (SINTA)

+
+
+ + + +
+ +
+
+

Directrices de Gestión Ambiental

+
+

Principales indicadores para caracterizar las organizaciones distinguidas por el programa de Directrices de Gestión Ambiental del Sistema Argentino de Calidad Turística.

+
+
+ + + +
+ +
+
+

Datos abiertos de turismo

+
+

Para consultar, descargar, usar, compartir.

+
+
+ + + +
+ +
+
+

Agencias de Viajes y de Turismo Estudiantil

+
+

Caracterización del sector en base al Registro de Agencias de Viajes de la Dirección Nacional de Agencias de Viajes (datos a marzo de 2022)

+
+
+ + + +
+ +
+
+

BIBLIOTECA

+
+

Este nuevo micrositio del Sistema de Información Turística de la Argentina (SINTA) reune investigaciones, documentos de trabajo, metodológicos y de coyuntura del sector turístico producidos por la DNMyE

+
+
+ + + +
+ +
+
+

Publicación del Informe Mensual de Estadísticas de Turismo

+
+

Publicación regular de la Dirección Nacional de Mercados y Estadística que compendia la última información disponible del turismo en Argentina y el mundo

+
+
+ + + +
+ +
+
+

{herramientas} para el procesamiento de datos y {comunicacion} de la DNMyE

+
+

Presentación de dos librerías con funciones del paquete estadístico R, desarrolladas por la Dirección Nacional de Mercados y Estadística del Ministerio de Turismo y Deportes de la Nación

+
+
+ + + +
+ +
+
+

MapeAr: una herramienta para visualizar información geográfica

+
+

¿Cómo convertir una base con datos georeferenciados en un mapa de Argentina?

+
+
+ + + +
+ +
+
+

Fuentes de datos: Padrón Único Nacional de Alojamiento (PUNA)

+
+

Siguiendo con la política de apertura de datos, la Dirección Nacional de Mercados y Estadística pone a disposición principales indicadores y la base de datos agregada de alojamientos colectivos 2020 del PUNA.

+
+
+ + + +
+ +
+
+

Fuentes de datos: Encuesta de Viajes y Turismo de los Hogares (EVyTH)

+
+

Características principales del relevamiento y potenciales usos de la información generada.

+
+
+ + + +
+ +
+
+

Fuentes de datos: Estimación Nacional del Turismo Internacional y Encuesta de Turismo Internacional (ETI)

+
+

Características principales del relevamiento y potenciales usos de la información generada.

+
+
+ + + +
+ +
+
+

Fuentes de datos: Encuesta de Ocupación Hotelera (EOH)

+
+

Características principales del relevamiento y potenciales usos de la información generada.

+
+
+ + + +
+ +
+
+

Publicación de microdatos de la Encuesta de Viajes y Turismo de los Hogares (EVyTH) - Serie 2012-2021

+
+

La Dirección Nacional de Mercados y Estadísticas pone a disposición la base de microdatos de la EVyTH-MINTURDEP con información desde los inicios del operativo hasta el último trimestre disponible.

+
+
+ + + +
+ +
+
+

CST-A PARTE II

+
+

Recorrido por las Tablas de la Cuenta Satélite de Turismo de Argentina (CST -A) identificando qué información nos ofrecen y cómo se interrelacionan para obtener el impacto económico del turismo

+
+
+ + + +
+ +
+
+

Turismo de Cultura

+
+

Principales indicadores para caracterizar los viajes y el perfil de las y los viajeros, nacionales y extranjeros, que realizan Turismo de Cultura en Argentina.

+
+
+ + + +
+ +
+
+

Turismo de Naturaleza

+
+

Principales indicadores para caracterizar los viajes y el perfil de las y los viajeros, nacionales y extranjeros, que realizan Turismo de Naturaleza en Argentina.

+
+
+ + + +
+ +
+
+

Tutorial SINTA

+
+

Un recorrido por el Sistema de Información Turística de la Argentina.

+
+
+ + + +
+ +
+
+

Monitor Estadístico de las Provincias

+
+

Compendio de los principales indicadores del turismo a nivel provincial

+
+
+ + + +
+ +
+
+

Publicación del Calendario de Difusión

+
+

Siguiendo con la política de apertura de información estadística, se pone a disposición el calendario de publicaciones de la Dirección de Mercados y Estadísticas, desde donde se podrá acceder a los productos estadísticos del área.

+
+
+ + + +
+ +
+
+

Publicación de la Cuenta Satélite de Turismo de la Argentina

+
+

La Cuenta Satélite de Turismo de la Argentina (CST-A) proporciona el marco para la medición de la contribución del sector turístico a la economía

+
+
+ + + +
+ +
+
+

Actualización de microdatos de la Encuesta de Viajes y Turismo de los Hogares (EVyTH)

+
+

Siguiendo con la política de apertura de datos, la Dirección Nacional de Mercados y Estadística (DNMyE) actualiza la base de microdatos de la EVyTH.

+
+
+ + + +
+ +
+
+

Empleo registrado en Turismo

+
+

Puestos de trabajo, empleo por género y empresas en ramas turísticas

+
+
+ + + +
+ +
+
+

Turismo en el Mercado Único y Libre de Cambios (MULC)

+
+

Ingresos y egresos de divisas vinculados al turismo en el mercado oficial de cambios.

+
+
+ + + +
+ +
+
+

Publicación de microdatos de la Encuesta de Viajes y Turismo de los Hogares (EVyTH)

+
+

Siguiendo con la política de apertura de datos, la Dirección Nacional de Mercados y Estadísticas pone a disposición la base de microdatos de la EVyTH.

+
+
+ + + +
+ +
+
+

Te damos la bienvenida a la Bitácora de Turismo

+
+

Un espacio virtual de la Dirección Nacional de Mercados y Estadística (DNMyE) en el que nos proponemos compartir avances de investigaciones, datos y reportes relacionados con el desarrollo de la industria turística de Argentina.

+
+
+
+ +
+

Novedades e Investigaciones

diff --git a/docs/posts/2023-12-14-comportamiento/comportamiento_files/header-attrs-2.20/header-attrs.js b/docs/posts/2023-12-14-comportamiento/comportamiento_files/header-attrs-2.20/header-attrs.js new file mode 100644 index 0000000..dd57d92 --- /dev/null +++ b/docs/posts/2023-12-14-comportamiento/comportamiento_files/header-attrs-2.20/header-attrs.js @@ -0,0 +1,12 @@ +// Pandoc 2.9 adds attributes on both header and div. We remove the former (to +// be compatible with the behavior of Pandoc < 2.8). +document.addEventListener('DOMContentLoaded', function(e) { + var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); + var i, h, a; + for (i = 0; i < hs.length; i++) { + h = hs[i]; + if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 + a = h.attributes; + while (a.length > 0) h.removeAttribute(a[0].name); + } +}); diff --git a/docs/posts/2023-12-14-comportamiento/comportamiento_files/htmlwidgets-1.6.1/htmlwidgets.js b/docs/posts/2023-12-14-comportamiento/comportamiento_files/htmlwidgets-1.6.1/htmlwidgets.js new file mode 100644 index 0000000..1067d02 --- /dev/null +++ b/docs/posts/2023-12-14-comportamiento/comportamiento_files/htmlwidgets-1.6.1/htmlwidgets.js @@ -0,0 +1,901 @@ +(function() { + // If window.HTMLWidgets is already defined, then use it; otherwise create a + // new object. This allows preceding code to set options that affect the + // initialization process (though none currently exist). + window.HTMLWidgets = window.HTMLWidgets || {}; + + // See if we're running in a viewer pane. If not, we're in a web browser. + var viewerMode = window.HTMLWidgets.viewerMode = + /\bviewer_pane=1\b/.test(window.location); + + // See if we're running in Shiny mode. If not, it's a static document. + // Note that static widgets can appear in both Shiny and static modes, but + // obviously, Shiny widgets can only appear in Shiny apps/documents. + var shinyMode = window.HTMLWidgets.shinyMode = + typeof(window.Shiny) !== "undefined" && !!window.Shiny.outputBindings; + + // We can't count on jQuery being available, so we implement our own + // version if necessary. + function querySelectorAll(scope, selector) { + if (typeof(jQuery) !== "undefined" && scope instanceof jQuery) { + return scope.find(selector); + } + if (scope.querySelectorAll) { + return scope.querySelectorAll(selector); + } + } + + function asArray(value) { + if (value === null) + return []; + if ($.isArray(value)) + return value; + return [value]; + } + + // Implement jQuery's extend + function extend(target /*, ... */) { + if (arguments.length == 1) { + return target; + } + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + target[prop] = source[prop]; + } + } + } + return target; + } + + // IE8 doesn't support Array.forEach. + function forEach(values, callback, thisArg) { + if (values.forEach) { + values.forEach(callback, thisArg); + } else { + for (var i = 0; i < values.length; i++) { + callback.call(thisArg, values[i], i, values); + } + } + } + + // Replaces the specified method with the return value of funcSource. + // + // Note that funcSource should not BE the new method, it should be a function + // that RETURNS the new method. funcSource receives a single argument that is + // the overridden method, it can be called from the new method. The overridden + // method can be called like a regular function, it has the target permanently + // bound to it so "this" will work correctly. + function overrideMethod(target, methodName, funcSource) { + var superFunc = target[methodName] || function() {}; + var superFuncBound = function() { + return superFunc.apply(target, arguments); + }; + target[methodName] = funcSource(superFuncBound); + } + + // Add a method to delegator that, when invoked, calls + // delegatee.methodName. If there is no such method on + // the delegatee, but there was one on delegator before + // delegateMethod was called, then the original version + // is invoked instead. + // For example: + // + // var a = { + // method1: function() { console.log('a1'); } + // method2: function() { console.log('a2'); } + // }; + // var b = { + // method1: function() { console.log('b1'); } + // }; + // delegateMethod(a, b, "method1"); + // delegateMethod(a, b, "method2"); + // a.method1(); + // a.method2(); + // + // The output would be "b1", "a2". + function delegateMethod(delegator, delegatee, methodName) { + var inherited = delegator[methodName]; + delegator[methodName] = function() { + var target = delegatee; + var method = delegatee[methodName]; + + // The method doesn't exist on the delegatee. Instead, + // call the method on the delegator, if it exists. + if (!method) { + target = delegator; + method = inherited; + } + + if (method) { + return method.apply(target, arguments); + } + }; + } + + // Implement a vague facsimilie of jQuery's data method + function elementData(el, name, value) { + if (arguments.length == 2) { + return el["htmlwidget_data_" + name]; + } else if (arguments.length == 3) { + el["htmlwidget_data_" + name] = value; + return el; + } else { + throw new Error("Wrong number of arguments for elementData: " + + arguments.length); + } + } + + // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex + function escapeRegExp(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + } + + function hasClass(el, className) { + var re = new RegExp("\\b" + escapeRegExp(className) + "\\b"); + return re.test(el.className); + } + + // elements - array (or array-like object) of HTML elements + // className - class name to test for + // include - if true, only return elements with given className; + // if false, only return elements *without* given className + function filterByClass(elements, className, include) { + var results = []; + for (var i = 0; i < elements.length; i++) { + if (hasClass(elements[i], className) == include) + results.push(elements[i]); + } + return results; + } + + function on(obj, eventName, func) { + if (obj.addEventListener) { + obj.addEventListener(eventName, func, false); + } else if (obj.attachEvent) { + obj.attachEvent(eventName, func); + } + } + + function off(obj, eventName, func) { + if (obj.removeEventListener) + obj.removeEventListener(eventName, func, false); + else if (obj.detachEvent) { + obj.detachEvent(eventName, func); + } + } + + // Translate array of values to top/right/bottom/left, as usual with + // the "padding" CSS property + // https://developer.mozilla.org/en-US/docs/Web/CSS/padding + function unpackPadding(value) { + if (typeof(value) === "number") + value = [value]; + if (value.length === 1) { + return {top: value[0], right: value[0], bottom: value[0], left: value[0]}; + } + if (value.length === 2) { + return {top: value[0], right: value[1], bottom: value[0], left: value[1]}; + } + if (value.length === 3) { + return {top: value[0], right: value[1], bottom: value[2], left: value[1]}; + } + if (value.length === 4) { + return {top: value[0], right: value[1], bottom: value[2], left: value[3]}; + } + } + + // Convert an unpacked padding object to a CSS value + function paddingToCss(paddingObj) { + return paddingObj.top + "px " + paddingObj.right + "px " + paddingObj.bottom + "px " + paddingObj.left + "px"; + } + + // Makes a number suitable for CSS + function px(x) { + if (typeof(x) === "number") + return x + "px"; + else + return x; + } + + // Retrieves runtime widget sizing information for an element. + // The return value is either null, or an object with fill, padding, + // defaultWidth, defaultHeight fields. + function sizingPolicy(el) { + var sizingEl = document.querySelector("script[data-for='" + el.id + "'][type='application/htmlwidget-sizing']"); + if (!sizingEl) + return null; + var sp = JSON.parse(sizingEl.textContent || sizingEl.text || "{}"); + if (viewerMode) { + return sp.viewer; + } else { + return sp.browser; + } + } + + // @param tasks Array of strings (or falsy value, in which case no-op). + // Each element must be a valid JavaScript expression that yields a + // function. Or, can be an array of objects with "code" and "data" + // properties; in this case, the "code" property should be a string + // of JS that's an expr that yields a function, and "data" should be + // an object that will be added as an additional argument when that + // function is called. + // @param target The object that will be "this" for each function + // execution. + // @param args Array of arguments to be passed to the functions. (The + // same arguments will be passed to all functions.) + function evalAndRun(tasks, target, args) { + if (tasks) { + forEach(tasks, function(task) { + var theseArgs = args; + if (typeof(task) === "object") { + theseArgs = theseArgs.concat([task.data]); + task = task.code; + } + var taskFunc = tryEval(task); + if (typeof(taskFunc) !== "function") { + throw new Error("Task must be a function! Source:\n" + task); + } + taskFunc.apply(target, theseArgs); + }); + } + } + + // Attempt eval() both with and without enclosing in parentheses. + // Note that enclosing coerces a function declaration into + // an expression that eval() can parse + // (otherwise, a SyntaxError is thrown) + function tryEval(code) { + var result = null; + try { + result = eval("(" + code + ")"); + } catch(error) { + if (!(error instanceof SyntaxError)) { + throw error; + } + try { + result = eval(code); + } catch(e) { + if (e instanceof SyntaxError) { + throw error; + } else { + throw e; + } + } + } + return result; + } + + function initSizing(el) { + var sizing = sizingPolicy(el); + if (!sizing) + return; + + var cel = document.getElementById("htmlwidget_container"); + if (!cel) + return; + + if (typeof(sizing.padding) !== "undefined") { + document.body.style.margin = "0"; + document.body.style.padding = paddingToCss(unpackPadding(sizing.padding)); + } + + if (sizing.fill) { + document.body.style.overflow = "hidden"; + document.body.style.width = "100%"; + document.body.style.height = "100%"; + document.documentElement.style.width = "100%"; + document.documentElement.style.height = "100%"; + cel.style.position = "absolute"; + var pad = unpackPadding(sizing.padding); + cel.style.top = pad.top + "px"; + cel.style.right = pad.right + "px"; + cel.style.bottom = pad.bottom + "px"; + cel.style.left = pad.left + "px"; + el.style.width = "100%"; + el.style.height = "100%"; + + return { + getWidth: function() { return cel.getBoundingClientRect().width; }, + getHeight: function() { return cel.getBoundingClientRect().height; } + }; + + } else { + el.style.width = px(sizing.width); + el.style.height = px(sizing.height); + + return { + getWidth: function() { return cel.getBoundingClientRect().width; }, + getHeight: function() { return cel.getBoundingClientRect().height; } + }; + } + } + + // Default implementations for methods + var defaults = { + find: function(scope) { + return querySelectorAll(scope, "." + this.name); + }, + renderError: function(el, err) { + var $el = $(el); + + this.clearError(el); + + // Add all these error classes, as Shiny does + var errClass = "shiny-output-error"; + if (err.type !== null) { + // use the classes of the error condition as CSS class names + errClass = errClass + " " + $.map(asArray(err.type), function(type) { + return errClass + "-" + type; + }).join(" "); + } + errClass = errClass + " htmlwidgets-error"; + + // Is el inline or block? If inline or inline-block, just display:none it + // and add an inline error. + var display = $el.css("display"); + $el.data("restore-display-mode", display); + + if (display === "inline" || display === "inline-block") { + $el.hide(); + if (err.message !== "") { + var errorSpan = $("").addClass(errClass); + errorSpan.text(err.message); + $el.after(errorSpan); + } + } else if (display === "block") { + // If block, add an error just after the el, set visibility:none on the + // el, and position the error to be on top of the el. + // Mark it with a unique ID and CSS class so we can remove it later. + $el.css("visibility", "hidden"); + if (err.message !== "") { + var errorDiv = $("
").addClass(errClass).css("position", "absolute") + .css("top", el.offsetTop) + .css("left", el.offsetLeft) + // setting width can push out the page size, forcing otherwise + // unnecessary scrollbars to appear and making it impossible for + // the element to shrink; so use max-width instead + .css("maxWidth", el.offsetWidth) + .css("height", el.offsetHeight); + errorDiv.text(err.message); + $el.after(errorDiv); + + // Really dumb way to keep the size/position of the error in sync with + // the parent element as the window is resized or whatever. + var intId = setInterval(function() { + if (!errorDiv[0].parentElement) { + clearInterval(intId); + return; + } + errorDiv + .css("top", el.offsetTop) + .css("left", el.offsetLeft) + .css("maxWidth", el.offsetWidth) + .css("height", el.offsetHeight); + }, 500); + } + } + }, + clearError: function(el) { + var $el = $(el); + var display = $el.data("restore-display-mode"); + $el.data("restore-display-mode", null); + + if (display === "inline" || display === "inline-block") { + if (display) + $el.css("display", display); + $(el.nextSibling).filter(".htmlwidgets-error").remove(); + } else if (display === "block"){ + $el.css("visibility", "inherit"); + $(el.nextSibling).filter(".htmlwidgets-error").remove(); + } + }, + sizing: {} + }; + + // Called by widget bindings to register a new type of widget. The definition + // object can contain the following properties: + // - name (required) - A string indicating the binding name, which will be + // used by default as the CSS classname to look for. + // - initialize (optional) - A function(el) that will be called once per + // widget element; if a value is returned, it will be passed as the third + // value to renderValue. + // - renderValue (required) - A function(el, data, initValue) that will be + // called with data. Static contexts will cause this to be called once per + // element; Shiny apps will cause this to be called multiple times per + // element, as the data changes. + window.HTMLWidgets.widget = function(definition) { + if (!definition.name) { + throw new Error("Widget must have a name"); + } + if (!definition.type) { + throw new Error("Widget must have a type"); + } + // Currently we only support output widgets + if (definition.type !== "output") { + throw new Error("Unrecognized widget type '" + definition.type + "'"); + } + // TODO: Verify that .name is a valid CSS classname + + // Support new-style instance-bound definitions. Old-style class-bound + // definitions have one widget "object" per widget per type/class of + // widget; the renderValue and resize methods on such widget objects + // take el and instance arguments, because the widget object can't + // store them. New-style instance-bound definitions have one widget + // object per widget instance; the definition that's passed in doesn't + // provide renderValue or resize methods at all, just the single method + // factory(el, width, height) + // which returns an object that has renderValue(x) and resize(w, h). + // This enables a far more natural programming style for the widget + // author, who can store per-instance state using either OO-style + // instance fields or functional-style closure variables (I guess this + // is in contrast to what can only be called C-style pseudo-OO which is + // what we required before). + if (definition.factory) { + definition = createLegacyDefinitionAdapter(definition); + } + + if (!definition.renderValue) { + throw new Error("Widget must have a renderValue function"); + } + + // For static rendering (non-Shiny), use a simple widget registration + // scheme. We also use this scheme for Shiny apps/documents that also + // contain static widgets. + window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || []; + // Merge defaults into the definition; don't mutate the original definition. + var staticBinding = extend({}, defaults, definition); + overrideMethod(staticBinding, "find", function(superfunc) { + return function(scope) { + var results = superfunc(scope); + // Filter out Shiny outputs, we only want the static kind + return filterByClass(results, "html-widget-output", false); + }; + }); + window.HTMLWidgets.widgets.push(staticBinding); + + if (shinyMode) { + // Shiny is running. Register the definition with an output binding. + // The definition itself will not be the output binding, instead + // we will make an output binding object that delegates to the + // definition. This is because we foolishly used the same method + // name (renderValue) for htmlwidgets definition and Shiny bindings + // but they actually have quite different semantics (the Shiny + // bindings receive data that includes lots of metadata that it + // strips off before calling htmlwidgets renderValue). We can't + // just ignore the difference because in some widgets it's helpful + // to call this.renderValue() from inside of resize(), and if + // we're not delegating, then that call will go to the Shiny + // version instead of the htmlwidgets version. + + // Merge defaults with definition, without mutating either. + var bindingDef = extend({}, defaults, definition); + + // This object will be our actual Shiny binding. + var shinyBinding = new Shiny.OutputBinding(); + + // With a few exceptions, we'll want to simply use the bindingDef's + // version of methods if they are available, otherwise fall back to + // Shiny's defaults. NOTE: If Shiny's output bindings gain additional + // methods in the future, and we want them to be overrideable by + // HTMLWidget binding definitions, then we'll need to add them to this + // list. + delegateMethod(shinyBinding, bindingDef, "getId"); + delegateMethod(shinyBinding, bindingDef, "onValueChange"); + delegateMethod(shinyBinding, bindingDef, "onValueError"); + delegateMethod(shinyBinding, bindingDef, "renderError"); + delegateMethod(shinyBinding, bindingDef, "clearError"); + delegateMethod(shinyBinding, bindingDef, "showProgress"); + + // The find, renderValue, and resize are handled differently, because we + // want to actually decorate the behavior of the bindingDef methods. + + shinyBinding.find = function(scope) { + var results = bindingDef.find(scope); + + // Only return elements that are Shiny outputs, not static ones + var dynamicResults = results.filter(".html-widget-output"); + + // It's possible that whatever caused Shiny to think there might be + // new dynamic outputs, also caused there to be new static outputs. + // Since there might be lots of different htmlwidgets bindings, we + // schedule execution for later--no need to staticRender multiple + // times. + if (results.length !== dynamicResults.length) + scheduleStaticRender(); + + return dynamicResults; + }; + + // Wrap renderValue to handle initialization, which unfortunately isn't + // supported natively by Shiny at the time of this writing. + + shinyBinding.renderValue = function(el, data) { + Shiny.renderDependencies(data.deps); + // Resolve strings marked as javascript literals to objects + if (!(data.evals instanceof Array)) data.evals = [data.evals]; + for (var i = 0; data.evals && i < data.evals.length; i++) { + window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]); + } + if (!bindingDef.renderOnNullValue) { + if (data.x === null) { + el.style.visibility = "hidden"; + return; + } else { + el.style.visibility = "inherit"; + } + } + if (!elementData(el, "initialized")) { + initSizing(el); + + elementData(el, "initialized", true); + if (bindingDef.initialize) { + var rect = el.getBoundingClientRect(); + var result = bindingDef.initialize(el, rect.width, rect.height); + elementData(el, "init_result", result); + } + } + bindingDef.renderValue(el, data.x, elementData(el, "init_result")); + evalAndRun(data.jsHooks.render, elementData(el, "init_result"), [el, data.x]); + }; + + // Only override resize if bindingDef implements it + if (bindingDef.resize) { + shinyBinding.resize = function(el, width, height) { + // Shiny can call resize before initialize/renderValue have been + // called, which doesn't make sense for widgets. + if (elementData(el, "initialized")) { + bindingDef.resize(el, width, height, elementData(el, "init_result")); + } + }; + } + + Shiny.outputBindings.register(shinyBinding, bindingDef.name); + } + }; + + var scheduleStaticRenderTimerId = null; + function scheduleStaticRender() { + if (!scheduleStaticRenderTimerId) { + scheduleStaticRenderTimerId = setTimeout(function() { + scheduleStaticRenderTimerId = null; + window.HTMLWidgets.staticRender(); + }, 1); + } + } + + // Render static widgets after the document finishes loading + // Statically render all elements that are of this widget's class + window.HTMLWidgets.staticRender = function() { + var bindings = window.HTMLWidgets.widgets || []; + forEach(bindings, function(binding) { + var matches = binding.find(document.documentElement); + forEach(matches, function(el) { + var sizeObj = initSizing(el, binding); + + var getSize = function(el) { + if (sizeObj) { + return {w: sizeObj.getWidth(), h: sizeObj.getHeight()} + } else { + var rect = el.getBoundingClientRect(); + return {w: rect.width, h: rect.height} + } + }; + + if (hasClass(el, "html-widget-static-bound")) + return; + el.className = el.className + " html-widget-static-bound"; + + var initResult; + if (binding.initialize) { + var size = getSize(el); + initResult = binding.initialize(el, size.w, size.h); + elementData(el, "init_result", initResult); + } + + if (binding.resize) { + var lastSize = getSize(el); + var resizeHandler = function(e) { + var size = getSize(el); + if (size.w === 0 && size.h === 0) + return; + if (size.w === lastSize.w && size.h === lastSize.h) + return; + lastSize = size; + binding.resize(el, size.w, size.h, initResult); + }; + + on(window, "resize", resizeHandler); + + // This is needed for cases where we're running in a Shiny + // app, but the widget itself is not a Shiny output, but + // rather a simple static widget. One example of this is + // an rmarkdown document that has runtime:shiny and widget + // that isn't in a render function. Shiny only knows to + // call resize handlers for Shiny outputs, not for static + // widgets, so we do it ourselves. + if (window.jQuery) { + window.jQuery(document).on( + "shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets", + resizeHandler + ); + window.jQuery(document).on( + "hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets", + resizeHandler + ); + } + + // This is needed for the specific case of ioslides, which + // flips slides between display:none and display:block. + // Ideally we would not have to have ioslide-specific code + // here, but rather have ioslides raise a generic event, + // but the rmarkdown package just went to CRAN so the + // window to getting that fixed may be long. + if (window.addEventListener) { + // It's OK to limit this to window.addEventListener + // browsers because ioslides itself only supports + // such browsers. + on(document, "slideenter", resizeHandler); + on(document, "slideleave", resizeHandler); + } + } + + var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']"); + if (scriptData) { + var data = JSON.parse(scriptData.textContent || scriptData.text); + // Resolve strings marked as javascript literals to objects + if (!(data.evals instanceof Array)) data.evals = [data.evals]; + for (var k = 0; data.evals && k < data.evals.length; k++) { + window.HTMLWidgets.evaluateStringMember(data.x, data.evals[k]); + } + binding.renderValue(el, data.x, initResult); + evalAndRun(data.jsHooks.render, initResult, [el, data.x]); + } + }); + }); + + invokePostRenderHandlers(); + } + + + function has_jQuery3() { + if (!window.jQuery) { + return false; + } + var $version = window.jQuery.fn.jquery; + var $major_version = parseInt($version.split(".")[0]); + return $major_version >= 3; + } + + /* + / Shiny 1.4 bumped jQuery from 1.x to 3.x which means jQuery's + / on-ready handler (i.e., $(fn)) is now asyncronous (i.e., it now + / really means $(setTimeout(fn)). + / https://jquery.com/upgrade-guide/3.0/#breaking-change-document-ready-handlers-are-now-asynchronous + / + / Since Shiny uses $() to schedule initShiny, shiny>=1.4 calls initShiny + / one tick later than it did before, which means staticRender() is + / called renderValue() earlier than (advanced) widget authors might be expecting. + / https://github.com/rstudio/shiny/issues/2630 + / + / For a concrete example, leaflet has some methods (e.g., updateBounds) + / which reference Shiny methods registered in initShiny (e.g., setInputValue). + / Since leaflet is privy to this life-cycle, it knows to use setTimeout() to + / delay execution of those methods (until Shiny methods are ready) + / https://github.com/rstudio/leaflet/blob/18ec981/javascript/src/index.js#L266-L268 + / + / Ideally widget authors wouldn't need to use this setTimeout() hack that + / leaflet uses to call Shiny methods on a staticRender(). In the long run, + / the logic initShiny should be broken up so that method registration happens + / right away, but binding happens later. + */ + function maybeStaticRenderLater() { + if (shinyMode && has_jQuery3()) { + window.jQuery(window.HTMLWidgets.staticRender); + } else { + window.HTMLWidgets.staticRender(); + } + } + + if (document.addEventListener) { + document.addEventListener("DOMContentLoaded", function() { + document.removeEventListener("DOMContentLoaded", arguments.callee, false); + maybeStaticRenderLater(); + }, false); + } else if (document.attachEvent) { + document.attachEvent("onreadystatechange", function() { + if (document.readyState === "complete") { + document.detachEvent("onreadystatechange", arguments.callee); + maybeStaticRenderLater(); + } + }); + } + + + window.HTMLWidgets.getAttachmentUrl = function(depname, key) { + // If no key, default to the first item + if (typeof(key) === "undefined") + key = 1; + + var link = document.getElementById(depname + "-" + key + "-attachment"); + if (!link) { + throw new Error("Attachment " + depname + "/" + key + " not found in document"); + } + return link.getAttribute("href"); + }; + + window.HTMLWidgets.dataframeToD3 = function(df) { + var names = []; + var length; + for (var name in df) { + if (df.hasOwnProperty(name)) + names.push(name); + if (typeof(df[name]) !== "object" || typeof(df[name].length) === "undefined") { + throw new Error("All fields must be arrays"); + } else if (typeof(length) !== "undefined" && length !== df[name].length) { + throw new Error("All fields must be arrays of the same length"); + } + length = df[name].length; + } + var results = []; + var item; + for (var row = 0; row < length; row++) { + item = {}; + for (var col = 0; col < names.length; col++) { + item[names[col]] = df[names[col]][row]; + } + results.push(item); + } + return results; + }; + + window.HTMLWidgets.transposeArray2D = function(array) { + if (array.length === 0) return array; + var newArray = array[0].map(function(col, i) { + return array.map(function(row) { + return row[i] + }) + }); + return newArray; + }; + // Split value at splitChar, but allow splitChar to be escaped + // using escapeChar. Any other characters escaped by escapeChar + // will be included as usual (including escapeChar itself). + function splitWithEscape(value, splitChar, escapeChar) { + var results = []; + var escapeMode = false; + var currentResult = ""; + for (var pos = 0; pos < value.length; pos++) { + if (!escapeMode) { + if (value[pos] === splitChar) { + results.push(currentResult); + currentResult = ""; + } else if (value[pos] === escapeChar) { + escapeMode = true; + } else { + currentResult += value[pos]; + } + } else { + currentResult += value[pos]; + escapeMode = false; + } + } + if (currentResult !== "") { + results.push(currentResult); + } + return results; + } + // Function authored by Yihui/JJ Allaire + window.HTMLWidgets.evaluateStringMember = function(o, member) { + var parts = splitWithEscape(member, '.', '\\'); + for (var i = 0, l = parts.length; i < l; i++) { + var part = parts[i]; + // part may be a character or 'numeric' member name + if (o !== null && typeof o === "object" && part in o) { + if (i == (l - 1)) { // if we are at the end of the line then evalulate + if (typeof o[part] === "string") + o[part] = tryEval(o[part]); + } else { // otherwise continue to next embedded object + o = o[part]; + } + } + } + }; + + // Retrieve the HTMLWidget instance (i.e. the return value of an + // HTMLWidget binding's initialize() or factory() function) + // associated with an element, or null if none. + window.HTMLWidgets.getInstance = function(el) { + return elementData(el, "init_result"); + }; + + // Finds the first element in the scope that matches the selector, + // and returns the HTMLWidget instance (i.e. the return value of + // an HTMLWidget binding's initialize() or factory() function) + // associated with that element, if any. If no element matches the + // selector, or the first matching element has no HTMLWidget + // instance associated with it, then null is returned. + // + // The scope argument is optional, and defaults to window.document. + window.HTMLWidgets.find = function(scope, selector) { + if (arguments.length == 1) { + selector = scope; + scope = document; + } + + var el = scope.querySelector(selector); + if (el === null) { + return null; + } else { + return window.HTMLWidgets.getInstance(el); + } + }; + + // Finds all elements in the scope that match the selector, and + // returns the HTMLWidget instances (i.e. the return values of + // an HTMLWidget binding's initialize() or factory() function) + // associated with the elements, in an array. If elements that + // match the selector don't have an associated HTMLWidget + // instance, the returned array will contain nulls. + // + // The scope argument is optional, and defaults to window.document. + window.HTMLWidgets.findAll = function(scope, selector) { + if (arguments.length == 1) { + selector = scope; + scope = document; + } + + var nodes = scope.querySelectorAll(selector); + var results = []; + for (var i = 0; i < nodes.length; i++) { + results.push(window.HTMLWidgets.getInstance(nodes[i])); + } + return results; + }; + + var postRenderHandlers = []; + function invokePostRenderHandlers() { + while (postRenderHandlers.length) { + var handler = postRenderHandlers.shift(); + if (handler) { + handler(); + } + } + } + + // Register the given callback function to be invoked after the + // next time static widgets are rendered. + window.HTMLWidgets.addPostRenderHandler = function(callback) { + postRenderHandlers.push(callback); + }; + + // Takes a new-style instance-bound definition, and returns an + // old-style class-bound definition. This saves us from having + // to rewrite all the logic in this file to accomodate both + // types of definitions. + function createLegacyDefinitionAdapter(defn) { + var result = { + name: defn.name, + type: defn.type, + initialize: function(el, width, height) { + return defn.factory(el, width, height); + }, + renderValue: function(el, x, instance) { + return instance.renderValue(x); + }, + resize: function(el, width, height, instance) { + return instance.resize(width, height); + } + }; + + if (defn.find) + result.find = defn.find; + if (defn.renderError) + result.renderError = defn.renderError; + if (defn.clearError) + result.clearError = defn.clearError; + + return result; + } +})(); diff --git a/docs/posts/2023-12-14-comportamiento/comportamiento_files/plotly-binding-4.10.2/plotly.js b/docs/posts/2023-12-14-comportamiento/comportamiento_files/plotly-binding-4.10.2/plotly.js new file mode 100644 index 0000000..7a2a143 --- /dev/null +++ b/docs/posts/2023-12-14-comportamiento/comportamiento_files/plotly-binding-4.10.2/plotly.js @@ -0,0 +1,941 @@ + +HTMLWidgets.widget({ + name: "plotly", + type: "output", + + initialize: function(el, width, height) { + return {}; + }, + + resize: function(el, width, height, instance) { + if (instance.autosize) { + var width = instance.width || width; + var height = instance.height || height; + Plotly.relayout(el.id, {width: width, height: height}); + } + }, + + renderValue: function(el, x, instance) { + + // Plotly.relayout() mutates the plot input object, so make sure to + // keep a reference to the user-supplied width/height *before* + // we call Plotly.plot(); + var lay = x.layout || {}; + instance.width = lay.width; + instance.height = lay.height; + instance.autosize = lay.autosize || true; + + /* + / 'inform the world' about highlighting options this is so other + / crosstalk libraries have a chance to respond to special settings + / such as persistent selection. + / AFAIK, leaflet is the only library with such intergration + / https://github.com/rstudio/leaflet/pull/346/files#diff-ad0c2d51ce5fdf8c90c7395b102f4265R154 + */ + var ctConfig = crosstalk.var('plotlyCrosstalkOpts').set(x.highlight); + + if (typeof(window) !== "undefined") { + // make sure plots don't get created outside the network (for on-prem) + window.PLOTLYENV = window.PLOTLYENV || {}; + window.PLOTLYENV.BASE_URL = x.base_url; + + // Enable persistent selection when shift key is down + // https://stackoverflow.com/questions/1828613/check-if-a-key-is-down + var persistOnShift = function(e) { + if (!e) window.event; + if (e.shiftKey) { + x.highlight.persistent = true; + x.highlight.persistentShift = true; + } else { + x.highlight.persistent = false; + x.highlight.persistentShift = false; + } + }; + + // Only relevant if we haven't forced persistent mode at command line + if (!x.highlight.persistent) { + window.onmousemove = persistOnShift; + } + } + + var graphDiv = document.getElementById(el.id); + + // TODO: move the control panel injection strategy inside here... + HTMLWidgets.addPostRenderHandler(function() { + + // lower the z-index of the modebar to prevent it from highjacking hover + // (TODO: do this via CSS?) + // https://github.com/ropensci/plotly/issues/956 + // https://www.w3schools.com/jsref/prop_style_zindex.asp + var modebars = document.querySelectorAll(".js-plotly-plot .plotly .modebar"); + for (var i = 0; i < modebars.length; i++) { + modebars[i].style.zIndex = 1; + } + }); + + // inject a "control panel" holding selectize/dynamic color widget(s) + if ((x.selectize || x.highlight.dynamic) && !instance.plotly) { + var flex = document.createElement("div"); + flex.class = "plotly-crosstalk-control-panel"; + flex.style = "display: flex; flex-wrap: wrap"; + + // inject the colourpicker HTML container into the flexbox + if (x.highlight.dynamic) { + var pickerDiv = document.createElement("div"); + + var pickerInput = document.createElement("input"); + pickerInput.id = el.id + "-colourpicker"; + pickerInput.placeholder = "asdasd"; + + var pickerLabel = document.createElement("label"); + pickerLabel.for = pickerInput.id; + pickerLabel.innerHTML = "Brush color  "; + + pickerDiv.appendChild(pickerLabel); + pickerDiv.appendChild(pickerInput); + flex.appendChild(pickerDiv); + } + + // inject selectize HTML containers (one for every crosstalk group) + if (x.selectize) { + var ids = Object.keys(x.selectize); + + for (var i = 0; i < ids.length; i++) { + var container = document.createElement("div"); + container.id = ids[i]; + container.style = "width: 80%; height: 10%"; + container.class = "form-group crosstalk-input-plotly-highlight"; + + var label = document.createElement("label"); + label.for = ids[i]; + label.innerHTML = x.selectize[ids[i]].group; + label.class = "control-label"; + + var selectDiv = document.createElement("div"); + var select = document.createElement("select"); + select.multiple = true; + + selectDiv.appendChild(select); + container.appendChild(label); + container.appendChild(selectDiv); + flex.appendChild(container); + } + } + + // finally, insert the flexbox inside the htmlwidget container, + // but before the plotly graph div + graphDiv.parentElement.insertBefore(flex, graphDiv); + + if (x.highlight.dynamic) { + var picker = $("#" + pickerInput.id); + var colors = x.highlight.color || []; + // TODO: let users specify options? + var opts = { + value: colors[0], + showColour: "both", + palette: "limited", + allowedCols: colors.join(" "), + width: "20%", + height: "10%" + }; + picker.colourpicker({changeDelay: 0}); + picker.colourpicker("settings", opts); + picker.colourpicker("value", opts.value); + // inform crosstalk about a change in the current selection colour + var grps = x.highlight.ctGroups || []; + for (var i = 0; i < grps.length; i++) { + crosstalk.group(grps[i]).var('plotlySelectionColour') + .set(picker.colourpicker('value')); + } + picker.on("change", function() { + for (var i = 0; i < grps.length; i++) { + crosstalk.group(grps[i]).var('plotlySelectionColour') + .set(picker.colourpicker('value')); + } + }); + } + } + + // if no plot exists yet, create one with a particular configuration + if (!instance.plotly) { + + var plot = Plotly.newPlot(graphDiv, x); + instance.plotly = true; + + } else if (x.layout.transition) { + + var plot = Plotly.react(graphDiv, x); + + } else { + + // this is essentially equivalent to Plotly.newPlot(), but avoids creating + // a new webgl context + // https://github.com/plotly/plotly.js/blob/2b24f9def901831e61282076cf3f835598d56f0e/src/plot_api/plot_api.js#L531-L532 + + // TODO: restore crosstalk selections? + Plotly.purge(graphDiv); + // TODO: why is this necessary to get crosstalk working? + graphDiv.data = undefined; + graphDiv.layout = undefined; + var plot = Plotly.newPlot(graphDiv, x); + } + + // Trigger plotly.js calls defined via `plotlyProxy()` + plot.then(function() { + if (HTMLWidgets.shinyMode) { + Shiny.addCustomMessageHandler("plotly-calls", function(msg) { + var gd = document.getElementById(msg.id); + if (!gd) { + throw new Error("Couldn't find plotly graph with id: " + msg.id); + } + // This isn't an official plotly.js method, but it's the only current way to + // change just the configuration of a plot + // https://community.plot.ly/t/update-config-function/9057 + if (msg.method == "reconfig") { + Plotly.react(gd, gd.data, gd.layout, msg.args); + return; + } + if (!Plotly[msg.method]) { + throw new Error("Unknown method " + msg.method); + } + var args = [gd].concat(msg.args); + Plotly[msg.method].apply(null, args); + }); + } + + // plotly's mapbox API doesn't currently support setting bounding boxes + // https://www.mapbox.com/mapbox-gl-js/example/fitbounds/ + // so we do this manually... + // TODO: make sure this triggers on a redraw and relayout as well as on initial draw + var mapboxIDs = graphDiv._fullLayout._subplots.mapbox || []; + for (var i = 0; i < mapboxIDs.length; i++) { + var id = mapboxIDs[i]; + var mapOpts = x.layout[id] || {}; + var args = mapOpts._fitBounds || {}; + if (!args) { + continue; + } + var mapObj = graphDiv._fullLayout[id]._subplot.map; + mapObj.fitBounds(args.bounds, args.options); + } + + }); + + // Attach attributes (e.g., "key", "z") to plotly event data + function eventDataWithKey(eventData) { + if (eventData === undefined || !eventData.hasOwnProperty("points")) { + return null; + } + return eventData.points.map(function(pt) { + var obj = { + curveNumber: pt.curveNumber, + pointNumber: pt.pointNumber, + x: pt.x, + y: pt.y + }; + + // If 'z' is reported with the event data, then use it! + if (pt.hasOwnProperty("z")) { + obj.z = pt.z; + } + + if (pt.hasOwnProperty("customdata")) { + obj.customdata = pt.customdata; + } + + /* + TL;DR: (I think) we have to select the graph div (again) to attach keys... + + Why? Remember that crosstalk will dynamically add/delete traces + (see traceManager.prototype.updateSelection() below) + For this reason, we can't simply grab keys from x.data (like we did previously) + Moreover, we can't use _fullData, since that doesn't include + unofficial attributes. It's true that click/hover events fire with + pt.data, but drag events don't... + */ + var gd = document.getElementById(el.id); + var trace = gd.data[pt.curveNumber]; + + if (!trace._isSimpleKey) { + var attrsToAttach = ["key"]; + } else { + // simple keys fire the whole key + obj.key = trace.key; + var attrsToAttach = []; + } + + for (var i = 0; i < attrsToAttach.length; i++) { + var attr = trace[attrsToAttach[i]]; + if (Array.isArray(attr)) { + if (typeof pt.pointNumber === "number") { + obj[attrsToAttach[i]] = attr[pt.pointNumber]; + } else if (Array.isArray(pt.pointNumber)) { + obj[attrsToAttach[i]] = attr[pt.pointNumber[0]][pt.pointNumber[1]]; + } else if (Array.isArray(pt.pointNumbers)) { + obj[attrsToAttach[i]] = pt.pointNumbers.map(function(idx) { return attr[idx]; }); + } + } + } + return obj; + }); + } + + + var legendEventData = function(d) { + // if legendgroup is not relevant just return the trace + var trace = d.data[d.curveNumber]; + if (!trace.legendgroup) return trace; + + // if legendgroup was specified, return all traces that match the group + var legendgrps = d.data.map(function(trace){ return trace.legendgroup; }); + var traces = []; + for (i = 0; i < legendgrps.length; i++) { + if (legendgrps[i] == trace.legendgroup) { + traces.push(d.data[i]); + } + } + + return traces; + }; + + + // send user input event data to shiny + if (HTMLWidgets.shinyMode && Shiny.setInputValue) { + + // Some events clear other input values + // TODO: always register these? + var eventClearMap = { + plotly_deselect: ["plotly_selected", "plotly_selecting", "plotly_brushed", "plotly_brushing", "plotly_click"], + plotly_unhover: ["plotly_hover"], + plotly_doubleclick: ["plotly_click"] + }; + + Object.keys(eventClearMap).map(function(evt) { + graphDiv.on(evt, function() { + var inputsToClear = eventClearMap[evt]; + inputsToClear.map(function(input) { + Shiny.setInputValue(input + "-" + x.source, null, {priority: "event"}); + }); + }); + }); + + var eventDataFunctionMap = { + plotly_click: eventDataWithKey, + plotly_sunburstclick: eventDataWithKey, + plotly_hover: eventDataWithKey, + plotly_unhover: eventDataWithKey, + // If 'plotly_selected' has already been fired, and you click + // on the plot afterwards, this event fires `undefined`?!? + // That might be considered a plotly.js bug, but it doesn't make + // sense for this input change to occur if `d` is falsy because, + // even in the empty selection case, `d` is truthy (an object), + // and the 'plotly_deselect' event will reset this input + plotly_selected: function(d) { if (d) { return eventDataWithKey(d); } }, + plotly_selecting: function(d) { if (d) { return eventDataWithKey(d); } }, + plotly_brushed: function(d) { + if (d) { return d.range ? d.range : d.lassoPoints; } + }, + plotly_brushing: function(d) { + if (d) { return d.range ? d.range : d.lassoPoints; } + }, + plotly_legendclick: legendEventData, + plotly_legenddoubleclick: legendEventData, + plotly_clickannotation: function(d) { return d.fullAnnotation } + }; + + var registerShinyValue = function(event) { + var eventDataPreProcessor = eventDataFunctionMap[event] || function(d) { return d ? d : el.id }; + // some events are unique to the R package + var plotlyJSevent = (event == "plotly_brushed") ? "plotly_selected" : (event == "plotly_brushing") ? "plotly_selecting" : event; + // register the event + graphDiv.on(plotlyJSevent, function(d) { + Shiny.setInputValue( + event + "-" + x.source, + JSON.stringify(eventDataPreProcessor(d)), + {priority: "event"} + ); + }); + } + + var shinyEvents = x.shinyEvents || []; + shinyEvents.map(registerShinyValue); + } + + // Given an array of {curveNumber: x, pointNumber: y} objects, + // return a hash of { + // set1: {value: [key1, key2, ...], _isSimpleKey: false}, + // set2: {value: [key3, key4, ...], _isSimpleKey: false} + // } + function pointsToKeys(points) { + var keysBySet = {}; + for (var i = 0; i < points.length; i++) { + + var trace = graphDiv.data[points[i].curveNumber]; + if (!trace.key || !trace.set) { + continue; + } + + // set defaults for this keySet + // note that we don't track the nested property (yet) since we always + // emit the union -- http://cpsievert.github.io/talks/20161212b/#21 + keysBySet[trace.set] = keysBySet[trace.set] || { + value: [], + _isSimpleKey: trace._isSimpleKey + }; + + // Use pointNumber by default, but aggregated traces should emit pointNumbers + var ptNum = points[i].pointNumber; + var hasPtNum = typeof ptNum === "number"; + var ptNum = hasPtNum ? ptNum : points[i].pointNumbers; + + // selecting a point of a "simple" trace means: select the + // entire key attached to this trace, which is useful for, + // say clicking on a fitted line to select corresponding observations + var key = trace._isSimpleKey ? trace.key : Array.isArray(ptNum) ? ptNum.map(function(idx) { return trace.key[idx]; }) : trace.key[ptNum]; + // http://stackoverflow.com/questions/10865025/merge-flatten-an-array-of-arrays-in-javascript + var keyFlat = trace._isNestedKey ? [].concat.apply([], key) : key; + + // TODO: better to only add new values? + keysBySet[trace.set].value = keysBySet[trace.set].value.concat(keyFlat); + } + + return keysBySet; + } + + + x.highlight.color = x.highlight.color || []; + // make sure highlight color is an array + if (!Array.isArray(x.highlight.color)) { + x.highlight.color = [x.highlight.color]; + } + + var traceManager = new TraceManager(graphDiv, x.highlight); + + // Gather all *unique* sets. + var allSets = []; + for (var curveIdx = 0; curveIdx < x.data.length; curveIdx++) { + var newSet = x.data[curveIdx].set; + if (newSet) { + if (allSets.indexOf(newSet) === -1) { + allSets.push(newSet); + } + } + } + + // register event listeners for all sets + for (var i = 0; i < allSets.length; i++) { + + var set = allSets[i]; + var selection = new crosstalk.SelectionHandle(set); + var filter = new crosstalk.FilterHandle(set); + + var filterChange = function(e) { + removeBrush(el); + traceManager.updateFilter(set, e.value); + }; + filter.on("change", filterChange); + + + var selectionChange = function(e) { + + // Workaround for 'plotly_selected' now firing previously selected + // points (in addition to new ones) when holding shift key. In our case, + // we just want the new keys + if (x.highlight.on === "plotly_selected" && x.highlight.persistentShift) { + // https://stackoverflow.com/questions/1187518/how-to-get-the-difference-between-two-arrays-in-javascript + Array.prototype.diff = function(a) { + return this.filter(function(i) {return a.indexOf(i) < 0;}); + }; + e.value = e.value.diff(e.oldValue); + } + + // array of "event objects" tracking the selection history + // this is used to avoid adding redundant selections + var selectionHistory = crosstalk.var("plotlySelectionHistory").get() || []; + + // Construct an event object "defining" the current event. + var event = { + receiverID: traceManager.gd.id, + plotlySelectionColour: crosstalk.group(set).var("plotlySelectionColour").get() + }; + event[set] = e.value; + // TODO: is there a smarter way to check object equality? + if (selectionHistory.length > 0) { + var ev = JSON.stringify(event); + for (var i = 0; i < selectionHistory.length; i++) { + var sel = JSON.stringify(selectionHistory[i]); + if (sel == ev) { + return; + } + } + } + + // accumulate history for persistent selection + if (!x.highlight.persistent) { + selectionHistory = [event]; + } else { + selectionHistory.push(event); + } + crosstalk.var("plotlySelectionHistory").set(selectionHistory); + + // do the actual updating of traces, frames, and the selectize widget + traceManager.updateSelection(set, e.value); + // https://github.com/selectize/selectize.js/blob/master/docs/api.md#methods_items + if (x.selectize) { + if (!x.highlight.persistent || e.value === null) { + selectize.clear(true); + } + selectize.addItems(e.value, true); + selectize.close(); + } + } + selection.on("change", selectionChange); + + // Set a crosstalk variable selection value, triggering an update + var turnOn = function(e) { + if (e) { + var selectedKeys = pointsToKeys(e.points); + // Keys are group names, values are array of selected keys from group. + for (var set in selectedKeys) { + if (selectedKeys.hasOwnProperty(set)) { + selection.set(selectedKeys[set].value, {sender: el}); + } + } + } + }; + if (x.highlight.debounce > 0) { + turnOn = debounce(turnOn, x.highlight.debounce); + } + graphDiv.on(x.highlight.on, turnOn); + + graphDiv.on(x.highlight.off, function turnOff(e) { + // remove any visual clues + removeBrush(el); + // remove any selection history + crosstalk.var("plotlySelectionHistory").set(null); + // trigger the actual removal of selection traces + selection.set(null, {sender: el}); + }); + + // register a callback for selectize so that there is bi-directional + // communication between the widget and direct manipulation events + if (x.selectize) { + var selectizeID = Object.keys(x.selectize)[i]; + var options = x.selectize[selectizeID]; + var first = [{value: "", label: "(All)"}]; + var opts = $.extend({ + options: first.concat(options.items), + searchField: "label", + valueField: "value", + labelField: "label", + maxItems: 50 + }, + options + ); + var select = $("#" + selectizeID).find("select")[0]; + var selectize = $(select).selectize(opts)[0].selectize; + // NOTE: this callback is triggered when *directly* altering + // dropdown items + selectize.on("change", function() { + var currentItems = traceManager.groupSelections[set] || []; + if (!x.highlight.persistent) { + removeBrush(el); + for (var i = 0; i < currentItems.length; i++) { + selectize.removeItem(currentItems[i], true); + } + } + var newItems = selectize.items.filter(function(idx) { + return currentItems.indexOf(idx) < 0; + }); + if (newItems.length > 0) { + traceManager.updateSelection(set, newItems); + } else { + // Item has been removed... + // TODO: this logic won't work for dynamically changing palette + traceManager.updateSelection(set, null); + traceManager.updateSelection(set, selectize.items); + } + }); + } + } // end of selectionChange + + } // end of renderValue +}); // end of widget definition + +/** + * @param graphDiv The Plotly graph div + * @param highlight An object with options for updating selection(s) + */ +function TraceManager(graphDiv, highlight) { + // The Plotly graph div + this.gd = graphDiv; + + // Preserve the original data. + // TODO: try using Lib.extendFlat() as done in + // https://github.com/plotly/plotly.js/pull/1136 + this.origData = JSON.parse(JSON.stringify(graphDiv.data)); + + // avoid doing this over and over + this.origOpacity = []; + for (var i = 0; i < this.origData.length; i++) { + this.origOpacity[i] = this.origData[i].opacity === 0 ? 0 : (this.origData[i].opacity || 1); + } + + // key: group name, value: null or array of keys representing the + // most recently received selection for that group. + this.groupSelections = {}; + + // selection parameters (e.g., transient versus persistent selection) + this.highlight = highlight; +} + +TraceManager.prototype.close = function() { + // TODO: Unhook all event handlers +}; + +TraceManager.prototype.updateFilter = function(group, keys) { + + if (typeof(keys) === "undefined" || keys === null) { + + this.gd.data = JSON.parse(JSON.stringify(this.origData)); + + } else { + + var traces = []; + for (var i = 0; i < this.origData.length; i++) { + var trace = this.origData[i]; + if (!trace.key || trace.set !== group) { + continue; + } + var matchFunc = getMatchFunc(trace); + var matches = matchFunc(trace.key, keys); + + if (matches.length > 0) { + if (!trace._isSimpleKey) { + // subsetArrayAttrs doesn't mutate trace (it makes a modified clone) + trace = subsetArrayAttrs(trace, matches); + } + traces.push(trace); + } + } + this.gd.data = traces; + } + + Plotly.redraw(this.gd); + + // NOTE: we purposely do _not_ restore selection(s), since on filter, + // axis likely will update, changing the pixel -> data mapping, leading + // to a likely mismatch in the brush outline and highlighted marks + +}; + +TraceManager.prototype.updateSelection = function(group, keys) { + + if (keys !== null && !Array.isArray(keys)) { + throw new Error("Invalid keys argument; null or array expected"); + } + + // if selection has been cleared, or if this is transient + // selection, delete the "selection traces" + var nNewTraces = this.gd.data.length - this.origData.length; + if (keys === null || !this.highlight.persistent && nNewTraces > 0) { + var tracesToRemove = []; + for (var i = 0; i < this.gd.data.length; i++) { + if (this.gd.data[i]._isCrosstalkTrace) tracesToRemove.push(i); + } + Plotly.deleteTraces(this.gd, tracesToRemove); + this.groupSelections[group] = keys; + } else { + // add to the groupSelection, rather than overwriting it + // TODO: can this be removed? + this.groupSelections[group] = this.groupSelections[group] || []; + for (var i = 0; i < keys.length; i++) { + var k = keys[i]; + if (this.groupSelections[group].indexOf(k) < 0) { + this.groupSelections[group].push(k); + } + } + } + + if (keys === null) { + + Plotly.restyle(this.gd, {"opacity": this.origOpacity}); + + } else if (keys.length >= 1) { + + // placeholder for new "selection traces" + var traces = []; + // this variable is set in R/highlight.R + var selectionColour = crosstalk.group(group).var("plotlySelectionColour").get() || + this.highlight.color[0]; + + for (var i = 0; i < this.origData.length; i++) { + // TODO: try using Lib.extendFlat() as done in + // https://github.com/plotly/plotly.js/pull/1136 + var trace = JSON.parse(JSON.stringify(this.gd.data[i])); + if (!trace.key || trace.set !== group) { + continue; + } + // Get sorted array of matching indices in trace.key + var matchFunc = getMatchFunc(trace); + var matches = matchFunc(trace.key, keys); + + if (matches.length > 0) { + // If this is a "simple" key, that means select the entire trace + if (!trace._isSimpleKey) { + trace = subsetArrayAttrs(trace, matches); + } + // reach into the full trace object so we can properly reflect the + // selection attributes in every view + var d = this.gd._fullData[i]; + + /* + / Recursively inherit selection attributes from various sources, + / in order of preference: + / (1) official plotly.js selected attribute + / (2) highlight(selected = attrs_selected(...)) + */ + // TODO: it would be neat to have a dropdown to dynamically specify these! + $.extend(true, trace, this.highlight.selected); + + // if it is defined, override color with the "dynamic brush color"" + if (d.marker) { + trace.marker = trace.marker || {}; + trace.marker.color = selectionColour || trace.marker.color || d.marker.color; + } + if (d.line) { + trace.line = trace.line || {}; + trace.line.color = selectionColour || trace.line.color || d.line.color; + } + if (d.textfont) { + trace.textfont = trace.textfont || {}; + trace.textfont.color = selectionColour || trace.textfont.color || d.textfont.color; + } + if (d.fillcolor) { + // TODO: should selectionColour inherit alpha from the existing fillcolor? + trace.fillcolor = selectionColour || trace.fillcolor || d.fillcolor; + } + // attach a sensible name/legendgroup + trace.name = trace.name || keys.join("
"); + trace.legendgroup = trace.legendgroup || keys.join("
"); + + // keep track of mapping between this new trace and the trace it targets + // (necessary for updating frames to reflect the selection traces) + trace._originalIndex = i; + trace._newIndex = this.gd._fullData.length + traces.length; + trace._isCrosstalkTrace = true; + traces.push(trace); + } + } + + if (traces.length > 0) { + + Plotly.addTraces(this.gd, traces).then(function(gd) { + // incrementally add selection traces to frames + // (this is heavily inspired by Plotly.Plots.modifyFrames() + // in src/plots/plots.js) + var _hash = gd._transitionData._frameHash; + var _frames = gd._transitionData._frames || []; + + for (var i = 0; i < _frames.length; i++) { + + // add to _frames[i].traces *if* this frame references selected trace(s) + var newIndices = []; + for (var j = 0; j < traces.length; j++) { + var tr = traces[j]; + if (_frames[i].traces.indexOf(tr._originalIndex) > -1) { + newIndices.push(tr._newIndex); + _frames[i].traces.push(tr._newIndex); + } + } + + // nothing to do... + if (newIndices.length === 0) { + continue; + } + + var ctr = 0; + var nFrameTraces = _frames[i].data.length; + + for (var j = 0; j < nFrameTraces; j++) { + var frameTrace = _frames[i].data[j]; + if (!frameTrace.key || frameTrace.set !== group) { + continue; + } + + var matchFunc = getMatchFunc(frameTrace); + var matches = matchFunc(frameTrace.key, keys); + + if (matches.length > 0) { + if (!trace._isSimpleKey) { + frameTrace = subsetArrayAttrs(frameTrace, matches); + } + var d = gd._fullData[newIndices[ctr]]; + if (d.marker) { + frameTrace.marker = d.marker; + } + if (d.line) { + frameTrace.line = d.line; + } + if (d.textfont) { + frameTrace.textfont = d.textfont; + } + ctr = ctr + 1; + _frames[i].data.push(frameTrace); + } + } + + // update gd._transitionData._frameHash + _hash[_frames[i].name] = _frames[i]; + } + + }); + + // dim traces that have a set matching the set of selection sets + var tracesToDim = [], + opacities = [], + sets = Object.keys(this.groupSelections), + n = this.origData.length; + + for (var i = 0; i < n; i++) { + var opacity = this.origOpacity[i] || 1; + // have we already dimmed this trace? Or is this even worth doing? + if (opacity !== this.gd._fullData[i].opacity || this.highlight.opacityDim === 1) { + continue; + } + // is this set an element of the set of selection sets? + var matches = findMatches(sets, [this.gd.data[i].set]); + if (matches.length) { + tracesToDim.push(i); + opacities.push(opacity * this.highlight.opacityDim); + } + } + + if (tracesToDim.length > 0) { + Plotly.restyle(this.gd, {"opacity": opacities}, tracesToDim); + // turn off the selected/unselected API + Plotly.restyle(this.gd, {"selectedpoints": null}); + } + + } + + } +}; + +/* +Note: in all of these match functions, we assume needleSet (i.e. the selected keys) +is a 1D (or flat) array. The real difference is the meaning of haystack. +findMatches() does the usual thing you'd expect for +linked brushing on a scatterplot matrix. findSimpleMatches() returns a match iff +haystack is a subset of the needleSet. findNestedMatches() returns +*/ + +function getMatchFunc(trace) { + return (trace._isNestedKey) ? findNestedMatches : + (trace._isSimpleKey) ? findSimpleMatches : findMatches; +} + +// find matches for "flat" keys +function findMatches(haystack, needleSet) { + var matches = []; + haystack.forEach(function(obj, i) { + if (obj === null || needleSet.indexOf(obj) >= 0) { + matches.push(i); + } + }); + return matches; +} + +// find matches for "simple" keys +function findSimpleMatches(haystack, needleSet) { + var match = haystack.every(function(val) { + return val === null || needleSet.indexOf(val) >= 0; + }); + // yes, this doesn't make much sense other than conforming + // to the output type of the other match functions + return (match) ? [0] : [] +} + +// find matches for a "nested" haystack (2D arrays) +function findNestedMatches(haystack, needleSet) { + var matches = []; + for (var i = 0; i < haystack.length; i++) { + var hay = haystack[i]; + var match = hay.every(function(val) { + return val === null || needleSet.indexOf(val) >= 0; + }); + if (match) { + matches.push(i); + } + } + return matches; +} + +function isPlainObject(obj) { + return ( + Object.prototype.toString.call(obj) === '[object Object]' && + Object.getPrototypeOf(obj) === Object.prototype + ); +} + +function subsetArrayAttrs(obj, indices) { + var newObj = {}; + Object.keys(obj).forEach(function(k) { + var val = obj[k]; + + if (k.charAt(0) === "_") { + newObj[k] = val; + } else if (k === "transforms" && Array.isArray(val)) { + newObj[k] = val.map(function(transform) { + return subsetArrayAttrs(transform, indices); + }); + } else if (k === "colorscale" && Array.isArray(val)) { + newObj[k] = val; + } else if (isPlainObject(val)) { + newObj[k] = subsetArrayAttrs(val, indices); + } else if (Array.isArray(val)) { + newObj[k] = subsetArray(val, indices); + } else { + newObj[k] = val; + } + }); + return newObj; +} + +function subsetArray(arr, indices) { + var result = []; + for (var i = 0; i < indices.length; i++) { + result.push(arr[indices[i]]); + } + return result; +} + +// Convenience function for removing plotly's brush +function removeBrush(el) { + var outlines = el.querySelectorAll(".select-outline"); + for (var i = 0; i < outlines.length; i++) { + outlines[i].remove(); + } +} + + +// https://davidwalsh.name/javascript-debounce-function + +// Returns a function, that, as long as it continues to be invoked, will not +// be triggered. The function will be called after it stops being called for +// N milliseconds. If `immediate` is passed, trigger the function on the +// leading edge, instead of the trailing. +function debounce(func, wait, immediate) { + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; +}; diff --git a/docs/posts/2023-12-14-comportamiento/imag1.PNG b/docs/posts/2023-12-14-comportamiento/imag1.PNG new file mode 100644 index 0000000000000000000000000000000000000000..906579431d5fc2627861962c798b6ff39d417204 GIT binary patch literal 24231 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 H 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 literal 0 HcmV?d00001 diff --git a/docs/posts/2023-12-14-comportamiento/index.html b/docs/posts/2023-12-14-comportamiento/index.html index 592b91a..6b41331 100644 --- a/docs/posts/2023-12-14-comportamiento/index.html +++ b/docs/posts/2023-12-14-comportamiento/index.html @@ -90,6 +90,7 @@ BITÁCORA: Comportamiento turístico de las personas, años 2021-2022 + @@ -100,23 +101,25 @@ + + @@ -463,8 +466,8 @@ - - + + @@ -2079,9 +2082,9 @@

${suggestion.title}

- - - + + + @@ -2262,7 +2265,7 @@

${suggestion.title}

@@ -2308,7 +2311,7 @@

${suggestion.title}

Comportamiento turístico de las personas, años 2021-2022

- +

Publicación de informes anuales con estimaciones sobre comportamiento turístico de las personas en base a la Encuesta de Viajes y Turismo de los Hogares (EVyTH)

@@ -2324,28 +2327,40 @@

Resultados

Entre los principales resultados que se presentan en el informe, se puede observar la evolución de la proporción de personas que realizaron al menos un viaje en el año 2021/2022. Los datos tanto de 2021 como de 2022, muestran una mejoría respecto al 20202, aunque aún no se recuperan los valores previos a la pandemia de covid.

Porcentaje de población residente en grandes aglomerados urbanos, de un año y más, que realizó al menos un viaje con pernocte. Años 2006, 2010 - 2022.

-
- +
+

Los datos se presentan desagregados por sexo, rango etario, región de origen de las personas, quintil de ingresos, y máximo nivel educativo alcanzado, lo que permite aproximar el análisis de viajes realizados, destino de los viajes o motivo de no viaje según variables socioeconómicas.

Los resultados demuestran que los quintiles más bajos no alcanzaron a recuperar los valores previos a la pandemia, como sí lo hizo el quintil más alto.

Porcentaje de población residente en grandes aglomerados urbanos, de un año y más, que realizó al menos un viaje con pernocte, según ingreso per cápita familiar. Años 2006, 2010-2022.

-
- +
+

Entre los principales motivos de no viaje en 2022 pondera la falta de dinero con un 49,2%, frente al 36,4% de 2021 cuando recién se estaba activando el turismo post pandemia y explica el aumento en la declaración de “otros motivos” que llegó a 19,4% en 2021, frente a 1,1% en 2022.

Distribución porcentual de población residente en grandes aglomerados urbanos, de un año y más, que no realizaron viajes con pernocte, según motivo principal por el que no viajaron. Años 2010 - 2022.

-
+
-
Razón por la que no realizó ningún viaje (%)(Base: Población no viajera)
Año
2010 2011 2012
- - +
+ + - - - + - + @@ -2941,11 +2971,11 @@

Recursos disponibles

Para recibir las novedades del SINTA escribíle al bot de Telegram de la DNMyE SintIA: @RDatinaBot 🤖

-
+

    -
  1. Por ejemplo, el relevamiento realizado en 2023 obtiene una base de datos con ventana de observación en 2022.↩︎

  2. -
  3. En el año 2020 se dispusieron por decreto medidas de emergencia sanitaria para contener la propagación del virus COVID-19 que restringían las salidas de la población. Estas medidas se mantuvieron desde Marzo de 2020 hasta el 31 de Diciembre de 2021.↩︎

  4. +
  5. Por ejemplo, el relevamiento realizado en 2023 obtiene una base de datos con ventana de observación en 2022.↩︎

  6. +
  7. En el año 2020 se dispusieron por decreto medidas de emergencia sanitaria para contener la propagación del virus COVID-19 que restringían las salidas de la población. Estas medidas se mantuvieron desde Marzo de 2020 hasta el 31 de Diciembre de 2021.↩︎

diff --git a/docs/posts/posts.json b/docs/posts/posts.json index 4f7b5a4..39cc22f 100644 --- a/docs/posts/posts.json +++ b/docs/posts/posts.json @@ -2,13 +2,13 @@ { "path": "posts/2023-12-14-comportamiento/", "title": "Comportamiento turístico de las personas, años 2021-2022", - "description": {}, + "description": "Publicación de informes anuales con estimaciones sobre comportamiento turístico de las personas en base a la Encuesta de Viajes y Turismo de los Hogares (EVyTH)", "author": [], "date": "2023-12-14", "categories": [], "contents": "\r\n\r\n💻 Informes: EVyTH\r\nPresentación\r\nLa Dirección Nacional de Mercados y Estadística (DNMyE) retoma la publicación anual de datos sobre el comportamiento turístico de las personas de los años 2021 y 2022.\r\nEl informe analiza las estimaciones de la proporción de personas residentes en los grandes aglomerados urbanos del país, de un año o más de edad, que realizaron al menos un viaje con pernocte. La información se desprende de un módulo especial de la EVyTH cuyo relevamiento se realiza entre los meses de Febrero y Mayo de cada año y recolecta información sobre la realización de viajes con pernocte, destino (Argentina o Exterior) y motivos de no viaje del año anterior1.\r\nLos gráficos presentados en este post son interactivos. Para visualizar el dato pasar el cursor del mouse sobre cada columna\r\nResultados\r\nEntre los principales resultados que se presentan en el informe, se puede observar la evolución de la proporción de personas que realizaron al menos un viaje en el año 2021/2022. Los datos tanto de 2021 como de 2022, muestran una mejoría respecto al 20202, aunque aún no se recuperan los valores previos a la pandemia de covid.\r\nPorcentaje de población residente en grandes aglomerados urbanos, de un año y más, que realizó al menos un viaje con pernocte. Años 2006, 2010 - 2022.\r\n\r\n\r\n\r\nLos datos se presentan desagregados por sexo, rango etario, región de origen de las personas, quintil de ingresos, y máximo nivel educativo alcanzado, lo que permite aproximar el análisis de viajes realizados, destino de los viajes o motivo de no viaje según variables socioeconómicas.\r\nLos resultados demuestran que los quintiles más bajos no alcanzaron a recuperar los valores previos a la pandemia, como sí lo hizo el quintil más alto.\r\nPorcentaje de población residente en grandes aglomerados urbanos, de un año y más, que realizó al menos un viaje con pernocte, según ingreso per cápita familiar. Años 2006, 2010-2022.\r\n\r\n\r\n\r\nEntre los principales motivos de no viaje en 2022 pondera la falta de dinero con un 49,2%, frente al 36,4% de 2021 cuando recién se estaba activando el turismo post pandemia y explica el aumento en la declaración de “otros motivos” que llegó a 19,4% en 2021, frente a 1,1% en 2022.\r\nDistribución porcentual de población residente en grandes aglomerados urbanos, de un año y más, que no realizaron viajes con pernocte, según motivo principal por el que no viajaron. Años 2010 - 2022.\r\n\r\n\r\nRazón por la que no realizó ningún viaje (%)(Base: Población no viajera)\r\n \r\n \r\n Año\r\n \r\n 2010\r\n 2011\r\n 2012\r\n 2013\r\n 2014\r\n 2015\r\n 2016\r\n 2017\r\n 2018\r\n 2019\r\n 2020\r\n 2021\r\n 2022\r\n Falta de dinero\r\n48,8\r\n48,1\r\n49,1\r\n52,3\r\n51,2\r\n49,3\r\n56,5\r\n58,1\r\n59,9\r\n55,6\r\n29,8\r\n36,4\r\n49,2Falta de tiempo\r\n8,4\r\n8,2\r\n7,5\r\n6,5\r\n6,9\r\n7,6\r\n6,6\r\n5,4\r\n4,5\r\n5,4\r\n10,2\r\n3,7\r\n5,7Menor de 14 años\r\n19,9\r\n22,2\r\n21,4\r\n20,6\r\n21,5\r\n21,4\r\n19,7\r\n19,6\r\n19,8\r\n19,4\r\n20,8\r\n19,1\r\n20,1Muy joven para viajar solo\r\n1,4\r\n2,4\r\n2,8\r\n4,2\r\n3,9\r\n5,0\r\n1,8\r\n2,2\r\n3,1\r\n2,3\r\n3,1\r\n2,6\r\n2,3No le gusta/no queria viajar\r\n5,1\r\n4,1\r\n2,8\r\n3,5\r\n2,6\r\n2,1\r\n2,1\r\n2,9\r\n2,3\r\n5,0\r\n4,8\r\n1,5\r\n3,9No lo tenia planificado\r\n6,1\r\n4,4\r\n5,5\r\n4,3\r\n4,9\r\n5,2\r\n4,6\r\n3,8\r\n3,5\r\n4,4\r\n8,6\r\n4,0\r\n6,6Problemas de salud\r\n5,8\r\n5,8\r\n5,2\r\n5,2\r\n5,5\r\n5,4\r\n5,6\r\n4,9\r\n4,0\r\n4,9\r\n7,1\r\n7,1\r\n6,2Problemas familiares\r\n3,0\r\n2,9\r\n3,0\r\n2,4\r\n2,6\r\n2,9\r\n2,0\r\n1,9\r\n2,2\r\n2,3\r\n1,0\r\n1,1\r\n2,2Otros\r\n0,2\r\n0,6\r\n0,4\r\n0,2\r\n0,3\r\n0,0\r\n0,1\r\n0,0\r\n0,1\r\n0,0\r\n13,8\r\n19,4\r\n1,1NS/NR\r\n1,3\r\n1,3\r\n2,2\r\n0,8\r\n0,5\r\n0,9\r\n1,0\r\n1,1\r\n0,6\r\n0,6\r\n0,7\r\n5,0\r\n2,7Fuente: DNMyE según base de datos EVYTH acumulada\r\n \r\n\r\nEntre algunos de los cambios que se presentan en esta edición, se incluyen más gráficos y una presentación de las tablas de anexo divididas en más partes con el fin de obtener una mejor visualización del dato.\r\nRecursos disponibles\r\nPara más información de la EVYTH se pueden consultar los siguientes recursos:\r\n📊 EVyTH, Datos abiertos\r\n📊 EVyTH, Informes técnicos\r\n🔗 Sistema de Información Turística de Argentina (SINTA)\r\n\r\nPara recibir las novedades del SINTA escribíle al bot de Telegram de la DNMyE SintIA: @RDatinaBot 🤖\r\n\r\n\r\nPor ejemplo, el relevamiento realizado en 2023 obtiene una base de datos con ventana de observación en 2022.↩︎\r\nEn el año 2020 se dispusieron por decreto medidas de emergencia sanitaria para contener la propagación del virus COVID-19 que restringían las salidas de la población. Estas medidas se mantuvieron desde Marzo de 2020 hasta el 31 de Diciembre de 2021.↩︎\r\n", - "preview": {}, - "last_modified": "2023-12-14T16:24:48-03:00", + "preview": "posts/2023-12-14-comportamiento/imag1.PNG", + "last_modified": "2023-12-20T15:01:52-03:00", "input_file": "comportamiento.knit.md" }, { diff --git a/docs/site_libs/htmlwidgets-1.6.2/htmlwidgets.js b/docs/site_libs/htmlwidgets-1.6.2/htmlwidgets.js new file mode 100644 index 0000000..1067d02 --- /dev/null +++ b/docs/site_libs/htmlwidgets-1.6.2/htmlwidgets.js @@ -0,0 +1,901 @@ +(function() { + // If window.HTMLWidgets is already defined, then use it; otherwise create a + // new object. This allows preceding code to set options that affect the + // initialization process (though none currently exist). + window.HTMLWidgets = window.HTMLWidgets || {}; + + // See if we're running in a viewer pane. If not, we're in a web browser. + var viewerMode = window.HTMLWidgets.viewerMode = + /\bviewer_pane=1\b/.test(window.location); + + // See if we're running in Shiny mode. If not, it's a static document. + // Note that static widgets can appear in both Shiny and static modes, but + // obviously, Shiny widgets can only appear in Shiny apps/documents. + var shinyMode = window.HTMLWidgets.shinyMode = + typeof(window.Shiny) !== "undefined" && !!window.Shiny.outputBindings; + + // We can't count on jQuery being available, so we implement our own + // version if necessary. + function querySelectorAll(scope, selector) { + if (typeof(jQuery) !== "undefined" && scope instanceof jQuery) { + return scope.find(selector); + } + if (scope.querySelectorAll) { + return scope.querySelectorAll(selector); + } + } + + function asArray(value) { + if (value === null) + return []; + if ($.isArray(value)) + return value; + return [value]; + } + + // Implement jQuery's extend + function extend(target /*, ... */) { + if (arguments.length == 1) { + return target; + } + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + target[prop] = source[prop]; + } + } + } + return target; + } + + // IE8 doesn't support Array.forEach. + function forEach(values, callback, thisArg) { + if (values.forEach) { + values.forEach(callback, thisArg); + } else { + for (var i = 0; i < values.length; i++) { + callback.call(thisArg, values[i], i, values); + } + } + } + + // Replaces the specified method with the return value of funcSource. + // + // Note that funcSource should not BE the new method, it should be a function + // that RETURNS the new method. funcSource receives a single argument that is + // the overridden method, it can be called from the new method. The overridden + // method can be called like a regular function, it has the target permanently + // bound to it so "this" will work correctly. + function overrideMethod(target, methodName, funcSource) { + var superFunc = target[methodName] || function() {}; + var superFuncBound = function() { + return superFunc.apply(target, arguments); + }; + target[methodName] = funcSource(superFuncBound); + } + + // Add a method to delegator that, when invoked, calls + // delegatee.methodName. If there is no such method on + // the delegatee, but there was one on delegator before + // delegateMethod was called, then the original version + // is invoked instead. + // For example: + // + // var a = { + // method1: function() { console.log('a1'); } + // method2: function() { console.log('a2'); } + // }; + // var b = { + // method1: function() { console.log('b1'); } + // }; + // delegateMethod(a, b, "method1"); + // delegateMethod(a, b, "method2"); + // a.method1(); + // a.method2(); + // + // The output would be "b1", "a2". + function delegateMethod(delegator, delegatee, methodName) { + var inherited = delegator[methodName]; + delegator[methodName] = function() { + var target = delegatee; + var method = delegatee[methodName]; + + // The method doesn't exist on the delegatee. Instead, + // call the method on the delegator, if it exists. + if (!method) { + target = delegator; + method = inherited; + } + + if (method) { + return method.apply(target, arguments); + } + }; + } + + // Implement a vague facsimilie of jQuery's data method + function elementData(el, name, value) { + if (arguments.length == 2) { + return el["htmlwidget_data_" + name]; + } else if (arguments.length == 3) { + el["htmlwidget_data_" + name] = value; + return el; + } else { + throw new Error("Wrong number of arguments for elementData: " + + arguments.length); + } + } + + // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex + function escapeRegExp(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + } + + function hasClass(el, className) { + var re = new RegExp("\\b" + escapeRegExp(className) + "\\b"); + return re.test(el.className); + } + + // elements - array (or array-like object) of HTML elements + // className - class name to test for + // include - if true, only return elements with given className; + // if false, only return elements *without* given className + function filterByClass(elements, className, include) { + var results = []; + for (var i = 0; i < elements.length; i++) { + if (hasClass(elements[i], className) == include) + results.push(elements[i]); + } + return results; + } + + function on(obj, eventName, func) { + if (obj.addEventListener) { + obj.addEventListener(eventName, func, false); + } else if (obj.attachEvent) { + obj.attachEvent(eventName, func); + } + } + + function off(obj, eventName, func) { + if (obj.removeEventListener) + obj.removeEventListener(eventName, func, false); + else if (obj.detachEvent) { + obj.detachEvent(eventName, func); + } + } + + // Translate array of values to top/right/bottom/left, as usual with + // the "padding" CSS property + // https://developer.mozilla.org/en-US/docs/Web/CSS/padding + function unpackPadding(value) { + if (typeof(value) === "number") + value = [value]; + if (value.length === 1) { + return {top: value[0], right: value[0], bottom: value[0], left: value[0]}; + } + if (value.length === 2) { + return {top: value[0], right: value[1], bottom: value[0], left: value[1]}; + } + if (value.length === 3) { + return {top: value[0], right: value[1], bottom: value[2], left: value[1]}; + } + if (value.length === 4) { + return {top: value[0], right: value[1], bottom: value[2], left: value[3]}; + } + } + + // Convert an unpacked padding object to a CSS value + function paddingToCss(paddingObj) { + return paddingObj.top + "px " + paddingObj.right + "px " + paddingObj.bottom + "px " + paddingObj.left + "px"; + } + + // Makes a number suitable for CSS + function px(x) { + if (typeof(x) === "number") + return x + "px"; + else + return x; + } + + // Retrieves runtime widget sizing information for an element. + // The return value is either null, or an object with fill, padding, + // defaultWidth, defaultHeight fields. + function sizingPolicy(el) { + var sizingEl = document.querySelector("script[data-for='" + el.id + "'][type='application/htmlwidget-sizing']"); + if (!sizingEl) + return null; + var sp = JSON.parse(sizingEl.textContent || sizingEl.text || "{}"); + if (viewerMode) { + return sp.viewer; + } else { + return sp.browser; + } + } + + // @param tasks Array of strings (or falsy value, in which case no-op). + // Each element must be a valid JavaScript expression that yields a + // function. Or, can be an array of objects with "code" and "data" + // properties; in this case, the "code" property should be a string + // of JS that's an expr that yields a function, and "data" should be + // an object that will be added as an additional argument when that + // function is called. + // @param target The object that will be "this" for each function + // execution. + // @param args Array of arguments to be passed to the functions. (The + // same arguments will be passed to all functions.) + function evalAndRun(tasks, target, args) { + if (tasks) { + forEach(tasks, function(task) { + var theseArgs = args; + if (typeof(task) === "object") { + theseArgs = theseArgs.concat([task.data]); + task = task.code; + } + var taskFunc = tryEval(task); + if (typeof(taskFunc) !== "function") { + throw new Error("Task must be a function! Source:\n" + task); + } + taskFunc.apply(target, theseArgs); + }); + } + } + + // Attempt eval() both with and without enclosing in parentheses. + // Note that enclosing coerces a function declaration into + // an expression that eval() can parse + // (otherwise, a SyntaxError is thrown) + function tryEval(code) { + var result = null; + try { + result = eval("(" + code + ")"); + } catch(error) { + if (!(error instanceof SyntaxError)) { + throw error; + } + try { + result = eval(code); + } catch(e) { + if (e instanceof SyntaxError) { + throw error; + } else { + throw e; + } + } + } + return result; + } + + function initSizing(el) { + var sizing = sizingPolicy(el); + if (!sizing) + return; + + var cel = document.getElementById("htmlwidget_container"); + if (!cel) + return; + + if (typeof(sizing.padding) !== "undefined") { + document.body.style.margin = "0"; + document.body.style.padding = paddingToCss(unpackPadding(sizing.padding)); + } + + if (sizing.fill) { + document.body.style.overflow = "hidden"; + document.body.style.width = "100%"; + document.body.style.height = "100%"; + document.documentElement.style.width = "100%"; + document.documentElement.style.height = "100%"; + cel.style.position = "absolute"; + var pad = unpackPadding(sizing.padding); + cel.style.top = pad.top + "px"; + cel.style.right = pad.right + "px"; + cel.style.bottom = pad.bottom + "px"; + cel.style.left = pad.left + "px"; + el.style.width = "100%"; + el.style.height = "100%"; + + return { + getWidth: function() { return cel.getBoundingClientRect().width; }, + getHeight: function() { return cel.getBoundingClientRect().height; } + }; + + } else { + el.style.width = px(sizing.width); + el.style.height = px(sizing.height); + + return { + getWidth: function() { return cel.getBoundingClientRect().width; }, + getHeight: function() { return cel.getBoundingClientRect().height; } + }; + } + } + + // Default implementations for methods + var defaults = { + find: function(scope) { + return querySelectorAll(scope, "." + this.name); + }, + renderError: function(el, err) { + var $el = $(el); + + this.clearError(el); + + // Add all these error classes, as Shiny does + var errClass = "shiny-output-error"; + if (err.type !== null) { + // use the classes of the error condition as CSS class names + errClass = errClass + " " + $.map(asArray(err.type), function(type) { + return errClass + "-" + type; + }).join(" "); + } + errClass = errClass + " htmlwidgets-error"; + + // Is el inline or block? If inline or inline-block, just display:none it + // and add an inline error. + var display = $el.css("display"); + $el.data("restore-display-mode", display); + + if (display === "inline" || display === "inline-block") { + $el.hide(); + if (err.message !== "") { + var errorSpan = $("").addClass(errClass); + errorSpan.text(err.message); + $el.after(errorSpan); + } + } else if (display === "block") { + // If block, add an error just after the el, set visibility:none on the + // el, and position the error to be on top of the el. + // Mark it with a unique ID and CSS class so we can remove it later. + $el.css("visibility", "hidden"); + if (err.message !== "") { + var errorDiv = $("
").addClass(errClass).css("position", "absolute") + .css("top", el.offsetTop) + .css("left", el.offsetLeft) + // setting width can push out the page size, forcing otherwise + // unnecessary scrollbars to appear and making it impossible for + // the element to shrink; so use max-width instead + .css("maxWidth", el.offsetWidth) + .css("height", el.offsetHeight); + errorDiv.text(err.message); + $el.after(errorDiv); + + // Really dumb way to keep the size/position of the error in sync with + // the parent element as the window is resized or whatever. + var intId = setInterval(function() { + if (!errorDiv[0].parentElement) { + clearInterval(intId); + return; + } + errorDiv + .css("top", el.offsetTop) + .css("left", el.offsetLeft) + .css("maxWidth", el.offsetWidth) + .css("height", el.offsetHeight); + }, 500); + } + } + }, + clearError: function(el) { + var $el = $(el); + var display = $el.data("restore-display-mode"); + $el.data("restore-display-mode", null); + + if (display === "inline" || display === "inline-block") { + if (display) + $el.css("display", display); + $(el.nextSibling).filter(".htmlwidgets-error").remove(); + } else if (display === "block"){ + $el.css("visibility", "inherit"); + $(el.nextSibling).filter(".htmlwidgets-error").remove(); + } + }, + sizing: {} + }; + + // Called by widget bindings to register a new type of widget. The definition + // object can contain the following properties: + // - name (required) - A string indicating the binding name, which will be + // used by default as the CSS classname to look for. + // - initialize (optional) - A function(el) that will be called once per + // widget element; if a value is returned, it will be passed as the third + // value to renderValue. + // - renderValue (required) - A function(el, data, initValue) that will be + // called with data. Static contexts will cause this to be called once per + // element; Shiny apps will cause this to be called multiple times per + // element, as the data changes. + window.HTMLWidgets.widget = function(definition) { + if (!definition.name) { + throw new Error("Widget must have a name"); + } + if (!definition.type) { + throw new Error("Widget must have a type"); + } + // Currently we only support output widgets + if (definition.type !== "output") { + throw new Error("Unrecognized widget type '" + definition.type + "'"); + } + // TODO: Verify that .name is a valid CSS classname + + // Support new-style instance-bound definitions. Old-style class-bound + // definitions have one widget "object" per widget per type/class of + // widget; the renderValue and resize methods on such widget objects + // take el and instance arguments, because the widget object can't + // store them. New-style instance-bound definitions have one widget + // object per widget instance; the definition that's passed in doesn't + // provide renderValue or resize methods at all, just the single method + // factory(el, width, height) + // which returns an object that has renderValue(x) and resize(w, h). + // This enables a far more natural programming style for the widget + // author, who can store per-instance state using either OO-style + // instance fields or functional-style closure variables (I guess this + // is in contrast to what can only be called C-style pseudo-OO which is + // what we required before). + if (definition.factory) { + definition = createLegacyDefinitionAdapter(definition); + } + + if (!definition.renderValue) { + throw new Error("Widget must have a renderValue function"); + } + + // For static rendering (non-Shiny), use a simple widget registration + // scheme. We also use this scheme for Shiny apps/documents that also + // contain static widgets. + window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || []; + // Merge defaults into the definition; don't mutate the original definition. + var staticBinding = extend({}, defaults, definition); + overrideMethod(staticBinding, "find", function(superfunc) { + return function(scope) { + var results = superfunc(scope); + // Filter out Shiny outputs, we only want the static kind + return filterByClass(results, "html-widget-output", false); + }; + }); + window.HTMLWidgets.widgets.push(staticBinding); + + if (shinyMode) { + // Shiny is running. Register the definition with an output binding. + // The definition itself will not be the output binding, instead + // we will make an output binding object that delegates to the + // definition. This is because we foolishly used the same method + // name (renderValue) for htmlwidgets definition and Shiny bindings + // but they actually have quite different semantics (the Shiny + // bindings receive data that includes lots of metadata that it + // strips off before calling htmlwidgets renderValue). We can't + // just ignore the difference because in some widgets it's helpful + // to call this.renderValue() from inside of resize(), and if + // we're not delegating, then that call will go to the Shiny + // version instead of the htmlwidgets version. + + // Merge defaults with definition, without mutating either. + var bindingDef = extend({}, defaults, definition); + + // This object will be our actual Shiny binding. + var shinyBinding = new Shiny.OutputBinding(); + + // With a few exceptions, we'll want to simply use the bindingDef's + // version of methods if they are available, otherwise fall back to + // Shiny's defaults. NOTE: If Shiny's output bindings gain additional + // methods in the future, and we want them to be overrideable by + // HTMLWidget binding definitions, then we'll need to add them to this + // list. + delegateMethod(shinyBinding, bindingDef, "getId"); + delegateMethod(shinyBinding, bindingDef, "onValueChange"); + delegateMethod(shinyBinding, bindingDef, "onValueError"); + delegateMethod(shinyBinding, bindingDef, "renderError"); + delegateMethod(shinyBinding, bindingDef, "clearError"); + delegateMethod(shinyBinding, bindingDef, "showProgress"); + + // The find, renderValue, and resize are handled differently, because we + // want to actually decorate the behavior of the bindingDef methods. + + shinyBinding.find = function(scope) { + var results = bindingDef.find(scope); + + // Only return elements that are Shiny outputs, not static ones + var dynamicResults = results.filter(".html-widget-output"); + + // It's possible that whatever caused Shiny to think there might be + // new dynamic outputs, also caused there to be new static outputs. + // Since there might be lots of different htmlwidgets bindings, we + // schedule execution for later--no need to staticRender multiple + // times. + if (results.length !== dynamicResults.length) + scheduleStaticRender(); + + return dynamicResults; + }; + + // Wrap renderValue to handle initialization, which unfortunately isn't + // supported natively by Shiny at the time of this writing. + + shinyBinding.renderValue = function(el, data) { + Shiny.renderDependencies(data.deps); + // Resolve strings marked as javascript literals to objects + if (!(data.evals instanceof Array)) data.evals = [data.evals]; + for (var i = 0; data.evals && i < data.evals.length; i++) { + window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]); + } + if (!bindingDef.renderOnNullValue) { + if (data.x === null) { + el.style.visibility = "hidden"; + return; + } else { + el.style.visibility = "inherit"; + } + } + if (!elementData(el, "initialized")) { + initSizing(el); + + elementData(el, "initialized", true); + if (bindingDef.initialize) { + var rect = el.getBoundingClientRect(); + var result = bindingDef.initialize(el, rect.width, rect.height); + elementData(el, "init_result", result); + } + } + bindingDef.renderValue(el, data.x, elementData(el, "init_result")); + evalAndRun(data.jsHooks.render, elementData(el, "init_result"), [el, data.x]); + }; + + // Only override resize if bindingDef implements it + if (bindingDef.resize) { + shinyBinding.resize = function(el, width, height) { + // Shiny can call resize before initialize/renderValue have been + // called, which doesn't make sense for widgets. + if (elementData(el, "initialized")) { + bindingDef.resize(el, width, height, elementData(el, "init_result")); + } + }; + } + + Shiny.outputBindings.register(shinyBinding, bindingDef.name); + } + }; + + var scheduleStaticRenderTimerId = null; + function scheduleStaticRender() { + if (!scheduleStaticRenderTimerId) { + scheduleStaticRenderTimerId = setTimeout(function() { + scheduleStaticRenderTimerId = null; + window.HTMLWidgets.staticRender(); + }, 1); + } + } + + // Render static widgets after the document finishes loading + // Statically render all elements that are of this widget's class + window.HTMLWidgets.staticRender = function() { + var bindings = window.HTMLWidgets.widgets || []; + forEach(bindings, function(binding) { + var matches = binding.find(document.documentElement); + forEach(matches, function(el) { + var sizeObj = initSizing(el, binding); + + var getSize = function(el) { + if (sizeObj) { + return {w: sizeObj.getWidth(), h: sizeObj.getHeight()} + } else { + var rect = el.getBoundingClientRect(); + return {w: rect.width, h: rect.height} + } + }; + + if (hasClass(el, "html-widget-static-bound")) + return; + el.className = el.className + " html-widget-static-bound"; + + var initResult; + if (binding.initialize) { + var size = getSize(el); + initResult = binding.initialize(el, size.w, size.h); + elementData(el, "init_result", initResult); + } + + if (binding.resize) { + var lastSize = getSize(el); + var resizeHandler = function(e) { + var size = getSize(el); + if (size.w === 0 && size.h === 0) + return; + if (size.w === lastSize.w && size.h === lastSize.h) + return; + lastSize = size; + binding.resize(el, size.w, size.h, initResult); + }; + + on(window, "resize", resizeHandler); + + // This is needed for cases where we're running in a Shiny + // app, but the widget itself is not a Shiny output, but + // rather a simple static widget. One example of this is + // an rmarkdown document that has runtime:shiny and widget + // that isn't in a render function. Shiny only knows to + // call resize handlers for Shiny outputs, not for static + // widgets, so we do it ourselves. + if (window.jQuery) { + window.jQuery(document).on( + "shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets", + resizeHandler + ); + window.jQuery(document).on( + "hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets", + resizeHandler + ); + } + + // This is needed for the specific case of ioslides, which + // flips slides between display:none and display:block. + // Ideally we would not have to have ioslide-specific code + // here, but rather have ioslides raise a generic event, + // but the rmarkdown package just went to CRAN so the + // window to getting that fixed may be long. + if (window.addEventListener) { + // It's OK to limit this to window.addEventListener + // browsers because ioslides itself only supports + // such browsers. + on(document, "slideenter", resizeHandler); + on(document, "slideleave", resizeHandler); + } + } + + var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']"); + if (scriptData) { + var data = JSON.parse(scriptData.textContent || scriptData.text); + // Resolve strings marked as javascript literals to objects + if (!(data.evals instanceof Array)) data.evals = [data.evals]; + for (var k = 0; data.evals && k < data.evals.length; k++) { + window.HTMLWidgets.evaluateStringMember(data.x, data.evals[k]); + } + binding.renderValue(el, data.x, initResult); + evalAndRun(data.jsHooks.render, initResult, [el, data.x]); + } + }); + }); + + invokePostRenderHandlers(); + } + + + function has_jQuery3() { + if (!window.jQuery) { + return false; + } + var $version = window.jQuery.fn.jquery; + var $major_version = parseInt($version.split(".")[0]); + return $major_version >= 3; + } + + /* + / Shiny 1.4 bumped jQuery from 1.x to 3.x which means jQuery's + / on-ready handler (i.e., $(fn)) is now asyncronous (i.e., it now + / really means $(setTimeout(fn)). + / https://jquery.com/upgrade-guide/3.0/#breaking-change-document-ready-handlers-are-now-asynchronous + / + / Since Shiny uses $() to schedule initShiny, shiny>=1.4 calls initShiny + / one tick later than it did before, which means staticRender() is + / called renderValue() earlier than (advanced) widget authors might be expecting. + / https://github.com/rstudio/shiny/issues/2630 + / + / For a concrete example, leaflet has some methods (e.g., updateBounds) + / which reference Shiny methods registered in initShiny (e.g., setInputValue). + / Since leaflet is privy to this life-cycle, it knows to use setTimeout() to + / delay execution of those methods (until Shiny methods are ready) + / https://github.com/rstudio/leaflet/blob/18ec981/javascript/src/index.js#L266-L268 + / + / Ideally widget authors wouldn't need to use this setTimeout() hack that + / leaflet uses to call Shiny methods on a staticRender(). In the long run, + / the logic initShiny should be broken up so that method registration happens + / right away, but binding happens later. + */ + function maybeStaticRenderLater() { + if (shinyMode && has_jQuery3()) { + window.jQuery(window.HTMLWidgets.staticRender); + } else { + window.HTMLWidgets.staticRender(); + } + } + + if (document.addEventListener) { + document.addEventListener("DOMContentLoaded", function() { + document.removeEventListener("DOMContentLoaded", arguments.callee, false); + maybeStaticRenderLater(); + }, false); + } else if (document.attachEvent) { + document.attachEvent("onreadystatechange", function() { + if (document.readyState === "complete") { + document.detachEvent("onreadystatechange", arguments.callee); + maybeStaticRenderLater(); + } + }); + } + + + window.HTMLWidgets.getAttachmentUrl = function(depname, key) { + // If no key, default to the first item + if (typeof(key) === "undefined") + key = 1; + + var link = document.getElementById(depname + "-" + key + "-attachment"); + if (!link) { + throw new Error("Attachment " + depname + "/" + key + " not found in document"); + } + return link.getAttribute("href"); + }; + + window.HTMLWidgets.dataframeToD3 = function(df) { + var names = []; + var length; + for (var name in df) { + if (df.hasOwnProperty(name)) + names.push(name); + if (typeof(df[name]) !== "object" || typeof(df[name].length) === "undefined") { + throw new Error("All fields must be arrays"); + } else if (typeof(length) !== "undefined" && length !== df[name].length) { + throw new Error("All fields must be arrays of the same length"); + } + length = df[name].length; + } + var results = []; + var item; + for (var row = 0; row < length; row++) { + item = {}; + for (var col = 0; col < names.length; col++) { + item[names[col]] = df[names[col]][row]; + } + results.push(item); + } + return results; + }; + + window.HTMLWidgets.transposeArray2D = function(array) { + if (array.length === 0) return array; + var newArray = array[0].map(function(col, i) { + return array.map(function(row) { + return row[i] + }) + }); + return newArray; + }; + // Split value at splitChar, but allow splitChar to be escaped + // using escapeChar. Any other characters escaped by escapeChar + // will be included as usual (including escapeChar itself). + function splitWithEscape(value, splitChar, escapeChar) { + var results = []; + var escapeMode = false; + var currentResult = ""; + for (var pos = 0; pos < value.length; pos++) { + if (!escapeMode) { + if (value[pos] === splitChar) { + results.push(currentResult); + currentResult = ""; + } else if (value[pos] === escapeChar) { + escapeMode = true; + } else { + currentResult += value[pos]; + } + } else { + currentResult += value[pos]; + escapeMode = false; + } + } + if (currentResult !== "") { + results.push(currentResult); + } + return results; + } + // Function authored by Yihui/JJ Allaire + window.HTMLWidgets.evaluateStringMember = function(o, member) { + var parts = splitWithEscape(member, '.', '\\'); + for (var i = 0, l = parts.length; i < l; i++) { + var part = parts[i]; + // part may be a character or 'numeric' member name + if (o !== null && typeof o === "object" && part in o) { + if (i == (l - 1)) { // if we are at the end of the line then evalulate + if (typeof o[part] === "string") + o[part] = tryEval(o[part]); + } else { // otherwise continue to next embedded object + o = o[part]; + } + } + } + }; + + // Retrieve the HTMLWidget instance (i.e. the return value of an + // HTMLWidget binding's initialize() or factory() function) + // associated with an element, or null if none. + window.HTMLWidgets.getInstance = function(el) { + return elementData(el, "init_result"); + }; + + // Finds the first element in the scope that matches the selector, + // and returns the HTMLWidget instance (i.e. the return value of + // an HTMLWidget binding's initialize() or factory() function) + // associated with that element, if any. If no element matches the + // selector, or the first matching element has no HTMLWidget + // instance associated with it, then null is returned. + // + // The scope argument is optional, and defaults to window.document. + window.HTMLWidgets.find = function(scope, selector) { + if (arguments.length == 1) { + selector = scope; + scope = document; + } + + var el = scope.querySelector(selector); + if (el === null) { + return null; + } else { + return window.HTMLWidgets.getInstance(el); + } + }; + + // Finds all elements in the scope that match the selector, and + // returns the HTMLWidget instances (i.e. the return values of + // an HTMLWidget binding's initialize() or factory() function) + // associated with the elements, in an array. If elements that + // match the selector don't have an associated HTMLWidget + // instance, the returned array will contain nulls. + // + // The scope argument is optional, and defaults to window.document. + window.HTMLWidgets.findAll = function(scope, selector) { + if (arguments.length == 1) { + selector = scope; + scope = document; + } + + var nodes = scope.querySelectorAll(selector); + var results = []; + for (var i = 0; i < nodes.length; i++) { + results.push(window.HTMLWidgets.getInstance(nodes[i])); + } + return results; + }; + + var postRenderHandlers = []; + function invokePostRenderHandlers() { + while (postRenderHandlers.length) { + var handler = postRenderHandlers.shift(); + if (handler) { + handler(); + } + } + } + + // Register the given callback function to be invoked after the + // next time static widgets are rendered. + window.HTMLWidgets.addPostRenderHandler = function(callback) { + postRenderHandlers.push(callback); + }; + + // Takes a new-style instance-bound definition, and returns an + // old-style class-bound definition. This saves us from having + // to rewrite all the logic in this file to accomodate both + // types of definitions. + function createLegacyDefinitionAdapter(defn) { + var result = { + name: defn.name, + type: defn.type, + initialize: function(el, width, height) { + return defn.factory(el, width, height); + }, + renderValue: function(el, x, instance) { + return instance.renderValue(x); + }, + resize: function(el, width, height, instance) { + return instance.resize(width, height); + } + }; + + if (defn.find) + result.find = defn.find; + if (defn.renderError) + result.renderError = defn.renderError; + if (defn.clearError) + result.clearError = defn.clearError; + + return result; + } +})(); diff --git a/docs/site_libs/plotly-binding-4.10.1/plotly.js b/docs/site_libs/plotly-binding-4.10.1/plotly.js new file mode 100644 index 0000000..1155269 --- /dev/null +++ b/docs/site_libs/plotly-binding-4.10.1/plotly.js @@ -0,0 +1,939 @@ + +HTMLWidgets.widget({ + name: "plotly", + type: "output", + + initialize: function(el, width, height) { + return {}; + }, + + resize: function(el, width, height, instance) { + if (instance.autosize) { + var width = instance.width || width; + var height = instance.height || height; + Plotly.relayout(el.id, {width: width, height: height}); + } + }, + + renderValue: function(el, x, instance) { + + // Plotly.relayout() mutates the plot input object, so make sure to + // keep a reference to the user-supplied width/height *before* + // we call Plotly.plot(); + var lay = x.layout || {}; + instance.width = lay.width; + instance.height = lay.height; + instance.autosize = lay.autosize || true; + + /* + / 'inform the world' about highlighting options this is so other + / crosstalk libraries have a chance to respond to special settings + / such as persistent selection. + / AFAIK, leaflet is the only library with such intergration + / https://github.com/rstudio/leaflet/pull/346/files#diff-ad0c2d51ce5fdf8c90c7395b102f4265R154 + */ + var ctConfig = crosstalk.var('plotlyCrosstalkOpts').set(x.highlight); + + if (typeof(window) !== "undefined") { + // make sure plots don't get created outside the network (for on-prem) + window.PLOTLYENV = window.PLOTLYENV || {}; + window.PLOTLYENV.BASE_URL = x.base_url; + + // Enable persistent selection when shift key is down + // https://stackoverflow.com/questions/1828613/check-if-a-key-is-down + var persistOnShift = function(e) { + if (!e) window.event; + if (e.shiftKey) { + x.highlight.persistent = true; + x.highlight.persistentShift = true; + } else { + x.highlight.persistent = false; + x.highlight.persistentShift = false; + } + }; + + // Only relevant if we haven't forced persistent mode at command line + if (!x.highlight.persistent) { + window.onmousemove = persistOnShift; + } + } + + var graphDiv = document.getElementById(el.id); + + // TODO: move the control panel injection strategy inside here... + HTMLWidgets.addPostRenderHandler(function() { + + // lower the z-index of the modebar to prevent it from highjacking hover + // (TODO: do this via CSS?) + // https://github.com/ropensci/plotly/issues/956 + // https://www.w3schools.com/jsref/prop_style_zindex.asp + var modebars = document.querySelectorAll(".js-plotly-plot .plotly .modebar"); + for (var i = 0; i < modebars.length; i++) { + modebars[i].style.zIndex = 1; + } + }); + + // inject a "control panel" holding selectize/dynamic color widget(s) + if ((x.selectize || x.highlight.dynamic) && !instance.plotly) { + var flex = document.createElement("div"); + flex.class = "plotly-crosstalk-control-panel"; + flex.style = "display: flex; flex-wrap: wrap"; + + // inject the colourpicker HTML container into the flexbox + if (x.highlight.dynamic) { + var pickerDiv = document.createElement("div"); + + var pickerInput = document.createElement("input"); + pickerInput.id = el.id + "-colourpicker"; + pickerInput.placeholder = "asdasd"; + + var pickerLabel = document.createElement("label"); + pickerLabel.for = pickerInput.id; + pickerLabel.innerHTML = "Brush color  "; + + pickerDiv.appendChild(pickerLabel); + pickerDiv.appendChild(pickerInput); + flex.appendChild(pickerDiv); + } + + // inject selectize HTML containers (one for every crosstalk group) + if (x.selectize) { + var ids = Object.keys(x.selectize); + + for (var i = 0; i < ids.length; i++) { + var container = document.createElement("div"); + container.id = ids[i]; + container.style = "width: 80%; height: 10%"; + container.class = "form-group crosstalk-input-plotly-highlight"; + + var label = document.createElement("label"); + label.for = ids[i]; + label.innerHTML = x.selectize[ids[i]].group; + label.class = "control-label"; + + var selectDiv = document.createElement("div"); + var select = document.createElement("select"); + select.multiple = true; + + selectDiv.appendChild(select); + container.appendChild(label); + container.appendChild(selectDiv); + flex.appendChild(container); + } + } + + // finally, insert the flexbox inside the htmlwidget container, + // but before the plotly graph div + graphDiv.parentElement.insertBefore(flex, graphDiv); + + if (x.highlight.dynamic) { + var picker = $("#" + pickerInput.id); + var colors = x.highlight.color || []; + // TODO: let users specify options? + var opts = { + value: colors[0], + showColour: "both", + palette: "limited", + allowedCols: colors.join(" "), + width: "20%", + height: "10%" + }; + picker.colourpicker({changeDelay: 0}); + picker.colourpicker("settings", opts); + picker.colourpicker("value", opts.value); + // inform crosstalk about a change in the current selection colour + var grps = x.highlight.ctGroups || []; + for (var i = 0; i < grps.length; i++) { + crosstalk.group(grps[i]).var('plotlySelectionColour') + .set(picker.colourpicker('value')); + } + picker.on("change", function() { + for (var i = 0; i < grps.length; i++) { + crosstalk.group(grps[i]).var('plotlySelectionColour') + .set(picker.colourpicker('value')); + } + }); + } + } + + // if no plot exists yet, create one with a particular configuration + if (!instance.plotly) { + + var plot = Plotly.newPlot(graphDiv, x); + instance.plotly = true; + + } else if (x.layout.transition) { + + var plot = Plotly.react(graphDiv, x); + + } else { + + // this is essentially equivalent to Plotly.newPlot(), but avoids creating + // a new webgl context + // https://github.com/plotly/plotly.js/blob/2b24f9def901831e61282076cf3f835598d56f0e/src/plot_api/plot_api.js#L531-L532 + + // TODO: restore crosstalk selections? + Plotly.purge(graphDiv); + // TODO: why is this necessary to get crosstalk working? + graphDiv.data = undefined; + graphDiv.layout = undefined; + var plot = Plotly.newPlot(graphDiv, x); + } + + // Trigger plotly.js calls defined via `plotlyProxy()` + plot.then(function() { + if (HTMLWidgets.shinyMode) { + Shiny.addCustomMessageHandler("plotly-calls", function(msg) { + var gd = document.getElementById(msg.id); + if (!gd) { + throw new Error("Couldn't find plotly graph with id: " + msg.id); + } + // This isn't an official plotly.js method, but it's the only current way to + // change just the configuration of a plot + // https://community.plot.ly/t/update-config-function/9057 + if (msg.method == "reconfig") { + Plotly.react(gd, gd.data, gd.layout, msg.args); + return; + } + if (!Plotly[msg.method]) { + throw new Error("Unknown method " + msg.method); + } + var args = [gd].concat(msg.args); + Plotly[msg.method].apply(null, args); + }); + } + + // plotly's mapbox API doesn't currently support setting bounding boxes + // https://www.mapbox.com/mapbox-gl-js/example/fitbounds/ + // so we do this manually... + // TODO: make sure this triggers on a redraw and relayout as well as on initial draw + var mapboxIDs = graphDiv._fullLayout._subplots.mapbox || []; + for (var i = 0; i < mapboxIDs.length; i++) { + var id = mapboxIDs[i]; + var mapOpts = x.layout[id] || {}; + var args = mapOpts._fitBounds || {}; + if (!args) { + continue; + } + var mapObj = graphDiv._fullLayout[id]._subplot.map; + mapObj.fitBounds(args.bounds, args.options); + } + + }); + + // Attach attributes (e.g., "key", "z") to plotly event data + function eventDataWithKey(eventData) { + if (eventData === undefined || !eventData.hasOwnProperty("points")) { + return null; + } + return eventData.points.map(function(pt) { + var obj = { + curveNumber: pt.curveNumber, + pointNumber: pt.pointNumber, + x: pt.x, + y: pt.y + }; + + // If 'z' is reported with the event data, then use it! + if (pt.hasOwnProperty("z")) { + obj.z = pt.z; + } + + if (pt.hasOwnProperty("customdata")) { + obj.customdata = pt.customdata; + } + + /* + TL;DR: (I think) we have to select the graph div (again) to attach keys... + + Why? Remember that crosstalk will dynamically add/delete traces + (see traceManager.prototype.updateSelection() below) + For this reason, we can't simply grab keys from x.data (like we did previously) + Moreover, we can't use _fullData, since that doesn't include + unofficial attributes. It's true that click/hover events fire with + pt.data, but drag events don't... + */ + var gd = document.getElementById(el.id); + var trace = gd.data[pt.curveNumber]; + + if (!trace._isSimpleKey) { + var attrsToAttach = ["key"]; + } else { + // simple keys fire the whole key + obj.key = trace.key; + var attrsToAttach = []; + } + + for (var i = 0; i < attrsToAttach.length; i++) { + var attr = trace[attrsToAttach[i]]; + if (Array.isArray(attr)) { + if (typeof pt.pointNumber === "number") { + obj[attrsToAttach[i]] = attr[pt.pointNumber]; + } else if (Array.isArray(pt.pointNumber)) { + obj[attrsToAttach[i]] = attr[pt.pointNumber[0]][pt.pointNumber[1]]; + } else if (Array.isArray(pt.pointNumbers)) { + obj[attrsToAttach[i]] = pt.pointNumbers.map(function(idx) { return attr[idx]; }); + } + } + } + return obj; + }); + } + + + var legendEventData = function(d) { + // if legendgroup is not relevant just return the trace + var trace = d.data[d.curveNumber]; + if (!trace.legendgroup) return trace; + + // if legendgroup was specified, return all traces that match the group + var legendgrps = d.data.map(function(trace){ return trace.legendgroup; }); + var traces = []; + for (i = 0; i < legendgrps.length; i++) { + if (legendgrps[i] == trace.legendgroup) { + traces.push(d.data[i]); + } + } + + return traces; + }; + + + // send user input event data to shiny + if (HTMLWidgets.shinyMode && Shiny.setInputValue) { + + // Some events clear other input values + // TODO: always register these? + var eventClearMap = { + plotly_deselect: ["plotly_selected", "plotly_selecting", "plotly_brushed", "plotly_brushing", "plotly_click"], + plotly_unhover: ["plotly_hover"], + plotly_doubleclick: ["plotly_click"] + }; + + Object.keys(eventClearMap).map(function(evt) { + graphDiv.on(evt, function() { + var inputsToClear = eventClearMap[evt]; + inputsToClear.map(function(input) { + Shiny.setInputValue(input + "-" + x.source, null, {priority: "event"}); + }); + }); + }); + + var eventDataFunctionMap = { + plotly_click: eventDataWithKey, + plotly_sunburstclick: eventDataWithKey, + plotly_hover: eventDataWithKey, + plotly_unhover: eventDataWithKey, + // If 'plotly_selected' has already been fired, and you click + // on the plot afterwards, this event fires `undefined`?!? + // That might be considered a plotly.js bug, but it doesn't make + // sense for this input change to occur if `d` is falsy because, + // even in the empty selection case, `d` is truthy (an object), + // and the 'plotly_deselect' event will reset this input + plotly_selected: function(d) { if (d) { return eventDataWithKey(d); } }, + plotly_selecting: function(d) { if (d) { return eventDataWithKey(d); } }, + plotly_brushed: function(d) { + if (d) { return d.range ? d.range : d.lassoPoints; } + }, + plotly_brushing: function(d) { + if (d) { return d.range ? d.range : d.lassoPoints; } + }, + plotly_legendclick: legendEventData, + plotly_legenddoubleclick: legendEventData, + plotly_clickannotation: function(d) { return d.fullAnnotation } + }; + + var registerShinyValue = function(event) { + var eventDataPreProcessor = eventDataFunctionMap[event] || function(d) { return d ? d : el.id }; + // some events are unique to the R package + var plotlyJSevent = (event == "plotly_brushed") ? "plotly_selected" : (event == "plotly_brushing") ? "plotly_selecting" : event; + // register the event + graphDiv.on(plotlyJSevent, function(d) { + Shiny.setInputValue( + event + "-" + x.source, + JSON.stringify(eventDataPreProcessor(d)), + {priority: "event"} + ); + }); + } + + var shinyEvents = x.shinyEvents || []; + shinyEvents.map(registerShinyValue); + } + + // Given an array of {curveNumber: x, pointNumber: y} objects, + // return a hash of { + // set1: {value: [key1, key2, ...], _isSimpleKey: false}, + // set2: {value: [key3, key4, ...], _isSimpleKey: false} + // } + function pointsToKeys(points) { + var keysBySet = {}; + for (var i = 0; i < points.length; i++) { + + var trace = graphDiv.data[points[i].curveNumber]; + if (!trace.key || !trace.set) { + continue; + } + + // set defaults for this keySet + // note that we don't track the nested property (yet) since we always + // emit the union -- http://cpsievert.github.io/talks/20161212b/#21 + keysBySet[trace.set] = keysBySet[trace.set] || { + value: [], + _isSimpleKey: trace._isSimpleKey + }; + + // Use pointNumber by default, but aggregated traces should emit pointNumbers + var ptNum = points[i].pointNumber; + var hasPtNum = typeof ptNum === "number"; + var ptNum = hasPtNum ? ptNum : points[i].pointNumbers; + + // selecting a point of a "simple" trace means: select the + // entire key attached to this trace, which is useful for, + // say clicking on a fitted line to select corresponding observations + var key = trace._isSimpleKey ? trace.key : Array.isArray(ptNum) ? ptNum.map(function(idx) { return trace.key[idx]; }) : trace.key[ptNum]; + // http://stackoverflow.com/questions/10865025/merge-flatten-an-array-of-arrays-in-javascript + var keyFlat = trace._isNestedKey ? [].concat.apply([], key) : key; + + // TODO: better to only add new values? + keysBySet[trace.set].value = keysBySet[trace.set].value.concat(keyFlat); + } + + return keysBySet; + } + + + x.highlight.color = x.highlight.color || []; + // make sure highlight color is an array + if (!Array.isArray(x.highlight.color)) { + x.highlight.color = [x.highlight.color]; + } + + var traceManager = new TraceManager(graphDiv, x.highlight); + + // Gather all *unique* sets. + var allSets = []; + for (var curveIdx = 0; curveIdx < x.data.length; curveIdx++) { + var newSet = x.data[curveIdx].set; + if (newSet) { + if (allSets.indexOf(newSet) === -1) { + allSets.push(newSet); + } + } + } + + // register event listeners for all sets + for (var i = 0; i < allSets.length; i++) { + + var set = allSets[i]; + var selection = new crosstalk.SelectionHandle(set); + var filter = new crosstalk.FilterHandle(set); + + var filterChange = function(e) { + removeBrush(el); + traceManager.updateFilter(set, e.value); + }; + filter.on("change", filterChange); + + + var selectionChange = function(e) { + + // Workaround for 'plotly_selected' now firing previously selected + // points (in addition to new ones) when holding shift key. In our case, + // we just want the new keys + if (x.highlight.on === "plotly_selected" && x.highlight.persistentShift) { + // https://stackoverflow.com/questions/1187518/how-to-get-the-difference-between-two-arrays-in-javascript + Array.prototype.diff = function(a) { + return this.filter(function(i) {return a.indexOf(i) < 0;}); + }; + e.value = e.value.diff(e.oldValue); + } + + // array of "event objects" tracking the selection history + // this is used to avoid adding redundant selections + var selectionHistory = crosstalk.var("plotlySelectionHistory").get() || []; + + // Construct an event object "defining" the current event. + var event = { + receiverID: traceManager.gd.id, + plotlySelectionColour: crosstalk.group(set).var("plotlySelectionColour").get() + }; + event[set] = e.value; + // TODO: is there a smarter way to check object equality? + if (selectionHistory.length > 0) { + var ev = JSON.stringify(event); + for (var i = 0; i < selectionHistory.length; i++) { + var sel = JSON.stringify(selectionHistory[i]); + if (sel == ev) { + return; + } + } + } + + // accumulate history for persistent selection + if (!x.highlight.persistent) { + selectionHistory = [event]; + } else { + selectionHistory.push(event); + } + crosstalk.var("plotlySelectionHistory").set(selectionHistory); + + // do the actual updating of traces, frames, and the selectize widget + traceManager.updateSelection(set, e.value); + // https://github.com/selectize/selectize.js/blob/master/docs/api.md#methods_items + if (x.selectize) { + if (!x.highlight.persistent || e.value === null) { + selectize.clear(true); + } + selectize.addItems(e.value, true); + selectize.close(); + } + } + selection.on("change", selectionChange); + + // Set a crosstalk variable selection value, triggering an update + var turnOn = function(e) { + if (e) { + var selectedKeys = pointsToKeys(e.points); + // Keys are group names, values are array of selected keys from group. + for (var set in selectedKeys) { + if (selectedKeys.hasOwnProperty(set)) { + selection.set(selectedKeys[set].value, {sender: el}); + } + } + } + }; + if (x.highlight.debounce > 0) { + turnOn = debounce(turnOn, x.highlight.debounce); + } + graphDiv.on(x.highlight.on, turnOn); + + graphDiv.on(x.highlight.off, function turnOff(e) { + // remove any visual clues + removeBrush(el); + // remove any selection history + crosstalk.var("plotlySelectionHistory").set(null); + // trigger the actual removal of selection traces + selection.set(null, {sender: el}); + }); + + // register a callback for selectize so that there is bi-directional + // communication between the widget and direct manipulation events + if (x.selectize) { + var selectizeID = Object.keys(x.selectize)[i]; + var items = x.selectize[selectizeID].items; + var first = [{value: "", label: "(All)"}]; + var opts = { + options: first.concat(items), + searchField: "label", + valueField: "value", + labelField: "label", + maxItems: 50 + }; + var select = $("#" + selectizeID).find("select")[0]; + var selectize = $(select).selectize(opts)[0].selectize; + // NOTE: this callback is triggered when *directly* altering + // dropdown items + selectize.on("change", function() { + var currentItems = traceManager.groupSelections[set] || []; + if (!x.highlight.persistent) { + removeBrush(el); + for (var i = 0; i < currentItems.length; i++) { + selectize.removeItem(currentItems[i], true); + } + } + var newItems = selectize.items.filter(function(idx) { + return currentItems.indexOf(idx) < 0; + }); + if (newItems.length > 0) { + traceManager.updateSelection(set, newItems); + } else { + // Item has been removed... + // TODO: this logic won't work for dynamically changing palette + traceManager.updateSelection(set, null); + traceManager.updateSelection(set, selectize.items); + } + }); + } + } // end of selectionChange + + } // end of renderValue +}); // end of widget definition + +/** + * @param graphDiv The Plotly graph div + * @param highlight An object with options for updating selection(s) + */ +function TraceManager(graphDiv, highlight) { + // The Plotly graph div + this.gd = graphDiv; + + // Preserve the original data. + // TODO: try using Lib.extendFlat() as done in + // https://github.com/plotly/plotly.js/pull/1136 + this.origData = JSON.parse(JSON.stringify(graphDiv.data)); + + // avoid doing this over and over + this.origOpacity = []; + for (var i = 0; i < this.origData.length; i++) { + this.origOpacity[i] = this.origData[i].opacity === 0 ? 0 : (this.origData[i].opacity || 1); + } + + // key: group name, value: null or array of keys representing the + // most recently received selection for that group. + this.groupSelections = {}; + + // selection parameters (e.g., transient versus persistent selection) + this.highlight = highlight; +} + +TraceManager.prototype.close = function() { + // TODO: Unhook all event handlers +}; + +TraceManager.prototype.updateFilter = function(group, keys) { + + if (typeof(keys) === "undefined" || keys === null) { + + this.gd.data = JSON.parse(JSON.stringify(this.origData)); + + } else { + + var traces = []; + for (var i = 0; i < this.origData.length; i++) { + var trace = this.origData[i]; + if (!trace.key || trace.set !== group) { + continue; + } + var matchFunc = getMatchFunc(trace); + var matches = matchFunc(trace.key, keys); + + if (matches.length > 0) { + if (!trace._isSimpleKey) { + // subsetArrayAttrs doesn't mutate trace (it makes a modified clone) + trace = subsetArrayAttrs(trace, matches); + } + traces.push(trace); + } + } + this.gd.data = traces; + } + + Plotly.redraw(this.gd); + + // NOTE: we purposely do _not_ restore selection(s), since on filter, + // axis likely will update, changing the pixel -> data mapping, leading + // to a likely mismatch in the brush outline and highlighted marks + +}; + +TraceManager.prototype.updateSelection = function(group, keys) { + + if (keys !== null && !Array.isArray(keys)) { + throw new Error("Invalid keys argument; null or array expected"); + } + + // if selection has been cleared, or if this is transient + // selection, delete the "selection traces" + var nNewTraces = this.gd.data.length - this.origData.length; + if (keys === null || !this.highlight.persistent && nNewTraces > 0) { + var tracesToRemove = []; + for (var i = 0; i < this.gd.data.length; i++) { + if (this.gd.data[i]._isCrosstalkTrace) tracesToRemove.push(i); + } + Plotly.deleteTraces(this.gd, tracesToRemove); + this.groupSelections[group] = keys; + } else { + // add to the groupSelection, rather than overwriting it + // TODO: can this be removed? + this.groupSelections[group] = this.groupSelections[group] || []; + for (var i = 0; i < keys.length; i++) { + var k = keys[i]; + if (this.groupSelections[group].indexOf(k) < 0) { + this.groupSelections[group].push(k); + } + } + } + + if (keys === null) { + + Plotly.restyle(this.gd, {"opacity": this.origOpacity}); + + } else if (keys.length >= 1) { + + // placeholder for new "selection traces" + var traces = []; + // this variable is set in R/highlight.R + var selectionColour = crosstalk.group(group).var("plotlySelectionColour").get() || + this.highlight.color[0]; + + for (var i = 0; i < this.origData.length; i++) { + // TODO: try using Lib.extendFlat() as done in + // https://github.com/plotly/plotly.js/pull/1136 + var trace = JSON.parse(JSON.stringify(this.gd.data[i])); + if (!trace.key || trace.set !== group) { + continue; + } + // Get sorted array of matching indices in trace.key + var matchFunc = getMatchFunc(trace); + var matches = matchFunc(trace.key, keys); + + if (matches.length > 0) { + // If this is a "simple" key, that means select the entire trace + if (!trace._isSimpleKey) { + trace = subsetArrayAttrs(trace, matches); + } + // reach into the full trace object so we can properly reflect the + // selection attributes in every view + var d = this.gd._fullData[i]; + + /* + / Recursively inherit selection attributes from various sources, + / in order of preference: + / (1) official plotly.js selected attribute + / (2) highlight(selected = attrs_selected(...)) + */ + // TODO: it would be neat to have a dropdown to dynamically specify these! + $.extend(true, trace, this.highlight.selected); + + // if it is defined, override color with the "dynamic brush color"" + if (d.marker) { + trace.marker = trace.marker || {}; + trace.marker.color = selectionColour || trace.marker.color || d.marker.color; + } + if (d.line) { + trace.line = trace.line || {}; + trace.line.color = selectionColour || trace.line.color || d.line.color; + } + if (d.textfont) { + trace.textfont = trace.textfont || {}; + trace.textfont.color = selectionColour || trace.textfont.color || d.textfont.color; + } + if (d.fillcolor) { + // TODO: should selectionColour inherit alpha from the existing fillcolor? + trace.fillcolor = selectionColour || trace.fillcolor || d.fillcolor; + } + // attach a sensible name/legendgroup + trace.name = trace.name || keys.join("
"); + trace.legendgroup = trace.legendgroup || keys.join("
"); + + // keep track of mapping between this new trace and the trace it targets + // (necessary for updating frames to reflect the selection traces) + trace._originalIndex = i; + trace._newIndex = this.gd._fullData.length + traces.length; + trace._isCrosstalkTrace = true; + traces.push(trace); + } + } + + if (traces.length > 0) { + + Plotly.addTraces(this.gd, traces).then(function(gd) { + // incrementally add selection traces to frames + // (this is heavily inspired by Plotly.Plots.modifyFrames() + // in src/plots/plots.js) + var _hash = gd._transitionData._frameHash; + var _frames = gd._transitionData._frames || []; + + for (var i = 0; i < _frames.length; i++) { + + // add to _frames[i].traces *if* this frame references selected trace(s) + var newIndices = []; + for (var j = 0; j < traces.length; j++) { + var tr = traces[j]; + if (_frames[i].traces.indexOf(tr._originalIndex) > -1) { + newIndices.push(tr._newIndex); + _frames[i].traces.push(tr._newIndex); + } + } + + // nothing to do... + if (newIndices.length === 0) { + continue; + } + + var ctr = 0; + var nFrameTraces = _frames[i].data.length; + + for (var j = 0; j < nFrameTraces; j++) { + var frameTrace = _frames[i].data[j]; + if (!frameTrace.key || frameTrace.set !== group) { + continue; + } + + var matchFunc = getMatchFunc(frameTrace); + var matches = matchFunc(frameTrace.key, keys); + + if (matches.length > 0) { + if (!trace._isSimpleKey) { + frameTrace = subsetArrayAttrs(frameTrace, matches); + } + var d = gd._fullData[newIndices[ctr]]; + if (d.marker) { + frameTrace.marker = d.marker; + } + if (d.line) { + frameTrace.line = d.line; + } + if (d.textfont) { + frameTrace.textfont = d.textfont; + } + ctr = ctr + 1; + _frames[i].data.push(frameTrace); + } + } + + // update gd._transitionData._frameHash + _hash[_frames[i].name] = _frames[i]; + } + + }); + + // dim traces that have a set matching the set of selection sets + var tracesToDim = [], + opacities = [], + sets = Object.keys(this.groupSelections), + n = this.origData.length; + + for (var i = 0; i < n; i++) { + var opacity = this.origOpacity[i] || 1; + // have we already dimmed this trace? Or is this even worth doing? + if (opacity !== this.gd._fullData[i].opacity || this.highlight.opacityDim === 1) { + continue; + } + // is this set an element of the set of selection sets? + var matches = findMatches(sets, [this.gd.data[i].set]); + if (matches.length) { + tracesToDim.push(i); + opacities.push(opacity * this.highlight.opacityDim); + } + } + + if (tracesToDim.length > 0) { + Plotly.restyle(this.gd, {"opacity": opacities}, tracesToDim); + // turn off the selected/unselected API + Plotly.restyle(this.gd, {"selectedpoints": null}); + } + + } + + } +}; + +/* +Note: in all of these match functions, we assume needleSet (i.e. the selected keys) +is a 1D (or flat) array. The real difference is the meaning of haystack. +findMatches() does the usual thing you'd expect for +linked brushing on a scatterplot matrix. findSimpleMatches() returns a match iff +haystack is a subset of the needleSet. findNestedMatches() returns +*/ + +function getMatchFunc(trace) { + return (trace._isNestedKey) ? findNestedMatches : + (trace._isSimpleKey) ? findSimpleMatches : findMatches; +} + +// find matches for "flat" keys +function findMatches(haystack, needleSet) { + var matches = []; + haystack.forEach(function(obj, i) { + if (obj === null || needleSet.indexOf(obj) >= 0) { + matches.push(i); + } + }); + return matches; +} + +// find matches for "simple" keys +function findSimpleMatches(haystack, needleSet) { + var match = haystack.every(function(val) { + return val === null || needleSet.indexOf(val) >= 0; + }); + // yes, this doesn't make much sense other than conforming + // to the output type of the other match functions + return (match) ? [0] : [] +} + +// find matches for a "nested" haystack (2D arrays) +function findNestedMatches(haystack, needleSet) { + var matches = []; + for (var i = 0; i < haystack.length; i++) { + var hay = haystack[i]; + var match = hay.every(function(val) { + return val === null || needleSet.indexOf(val) >= 0; + }); + if (match) { + matches.push(i); + } + } + return matches; +} + +function isPlainObject(obj) { + return ( + Object.prototype.toString.call(obj) === '[object Object]' && + Object.getPrototypeOf(obj) === Object.prototype + ); +} + +function subsetArrayAttrs(obj, indices) { + var newObj = {}; + Object.keys(obj).forEach(function(k) { + var val = obj[k]; + + if (k.charAt(0) === "_") { + newObj[k] = val; + } else if (k === "transforms" && Array.isArray(val)) { + newObj[k] = val.map(function(transform) { + return subsetArrayAttrs(transform, indices); + }); + } else if (k === "colorscale" && Array.isArray(val)) { + newObj[k] = val; + } else if (isPlainObject(val)) { + newObj[k] = subsetArrayAttrs(val, indices); + } else if (Array.isArray(val)) { + newObj[k] = subsetArray(val, indices); + } else { + newObj[k] = val; + } + }); + return newObj; +} + +function subsetArray(arr, indices) { + var result = []; + for (var i = 0; i < indices.length; i++) { + result.push(arr[indices[i]]); + } + return result; +} + +// Convenience function for removing plotly's brush +function removeBrush(el) { + var outlines = el.querySelectorAll(".select-outline"); + for (var i = 0; i < outlines.length; i++) { + outlines[i].remove(); + } +} + + +// https://davidwalsh.name/javascript-debounce-function + +// Returns a function, that, as long as it continues to be invoked, will not +// be triggered. The function will be called after it stops being called for +// N milliseconds. If `immediate` is passed, trigger the function on the +// leading edge, instead of the trailing. +function debounce(func, wait, immediate) { + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; +};
Razón por la que no realizó ningún viaje (%)(Base: Población no viajera)
Año
2010 2011 2012