diff --git a/DESCRIPTION b/DESCRIPTION index 03764e0..d90d2f7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: eurlex Type: Package Title: Retrieve Data on European Union Law -Version: 0.4.6 +Version: 0.4.7 Authors@R: c(person(given = "Michal", family = "Ovadek", role = c("aut", "cre", "cph"), @@ -12,7 +12,7 @@ License: GPL-3 Encoding: UTF-8 Language: en-US Depends: - R (>= 3.4.0) + R (>= 3.5.0) Imports: magrittr, dplyr, @@ -36,7 +36,7 @@ Suggests: ggiraph, testthat (>= 3.0.0) URL: https://michalovadek.github.io/eurlex/ -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.1 Roxygen: list(markdown = TRUE) VignetteBuilder: knitr Config/testthat/edition: 3 diff --git a/NEWS.md b/NEWS.md index 466515a..025c7e0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,9 @@ +# eurlex 0.4.7 + +## Minor changes +- some http calls were still not failing gracefully +- some leftover .data in tidyselect + # eurlex 0.4.6 ## Minor changes diff --git a/R/elx_curia_list.R b/R/elx_curia_list.R index 4765282..7ad36e1 100644 --- a/R/elx_curia_list.R +++ b/R/elx_curia_list.R @@ -33,7 +33,6 @@ elx_curia_list <- function(data = c("all","ecj_old","ecj_new","gc_all","cst_all" else {data <- match.arg(data)} - if (data == "all"){ res_c1 <- elx_curia_scraper(url_c1) @@ -89,7 +88,6 @@ elx_curia_list <- function(data = c("all","ecj_old","ecj_new","gc_all","cst_all" } - } #' Curia scraper function @@ -118,8 +116,8 @@ elx_curia_scraper <- function(url, ...){ dplyr::mutate(dplyr::across(.cols = dplyr::everything(), .fns = ~dplyr::na_if(., ""))) %>% dplyr::filter(!is.na(.data$X1) & !is.na(.data$X2)) %>% - dplyr::rename(case_id = .data$X1, - case_info = .data$X2) %>% + dplyr::rename(case_id = "X1", + case_info = "X2") %>% dplyr::group_by(.data$case_id) %>% dplyr::mutate(n_id = dplyr::row_number()) %>% dplyr::ungroup() @@ -127,7 +125,7 @@ elx_curia_scraper <- function(url, ...){ hrefs <- page %>% xml2::xml_find_all('//a[contains(@href, "numdoc")]') - linked_id <- rvest::html_text(hrefs, "href") + linked_id <- rvest::html_text(hrefs, trim = TRUE) linked_celex <- rvest::html_attr(hrefs, "href") %>% stringr::str_extract("numdoc=.*") %>% @@ -141,7 +139,7 @@ elx_curia_scraper <- function(url, ...){ out <- dplyr::left_join(tab, linked, by = c("case_id"="linked_id","n_id"="n_id")) %>% dplyr::select("case_id", "linked_celex", "case_info") %>% - dplyr::rename(case_id_celex = linked_celex) + dplyr::rename(case_id_celex = "linked_celex") return(out) diff --git a/R/elx_run_query.R b/R/elx_run_query.R index 40fbf65..d06dc98 100644 --- a/R/elx_run_query.R +++ b/R/elx_run_query.R @@ -59,6 +59,11 @@ graceful_http <- function(remote_file, headers = NULL, body = NULL, return(invisible(NULL)) } + # if missing verb pick GET + if (missing(verb)){ + verb <- "GET" + } + # Make the HTTP request based on the verb make_request <- function(verb) { tryCatch({ @@ -72,11 +77,11 @@ graceful_http <- function(remote_file, headers = NULL, body = NULL, } }, error = function(e) { - message("Error: ", conditionMessage(e)) + message(conditionMessage(e), " (Error)") return(invisible(NULL)) }, warning = function(w) { - message("Warning: ", conditionMessage(w)) + message(conditionMessage(w), " (Warning)") return(invisible(NULL)) }) } @@ -84,13 +89,9 @@ graceful_http <- function(remote_file, headers = NULL, body = NULL, # Execute the request resp <- make_request(verb) - # Check for HTTP errors - if (httr::http_error(resp)) { - httr::message_for_status(resp) - return(invisible(NULL)) - } - + # return return(resp) + } #' Parse RDF/XML triplets to data frame diff --git a/docs/404.html b/docs/404.html index 89e0462..4cc0c41 100644 --- a/docs/404.html +++ b/docs/404.html @@ -39,7 +39,7 @@ eurlex - 0.4.6 + 0.4.7 diff --git a/docs/articles/council.html b/docs/articles/council.html index dce79f6..3f8bc59 100644 --- a/docs/articles/council.html +++ b/docs/articles/council.html @@ -40,7 +40,7 @@ eurlex - 0.4.6 + 0.4.7 @@ -81,8 +81,8 @@ - -
+ +

Excluding votes where all governments voted in favour, we are left -with between 109 and 81 votes per Member +with between 110 and 81 votes per Member State. While these numbers do not represent the entire historical voting record, they should still help us lift the veil on variation in Member States’ propensity to disagree. Note that due to opt-outs not all @@ -224,8 +224,8 @@

Interactive plot opts_hover_inv(css = "opacity:0.1;"), opts_hover(css = "fill:green;")) )

-
-

The country comparison reveals substantial variation in the frequency +

+

The country comparison reveals substantial variation in the frequency of disagreement. The only Member State to ever exit the EU, the United Kingdom, was particularly active when it comes to abstaining or voting against legislation. On the other end of the scale is France, which has diff --git a/docs/articles/council_files/girafe-binding-0.8.8/girafe.js b/docs/articles/council_files/girafe-binding-0.8.8/girafe.js new file mode 100644 index 0000000..1c6a1ce --- /dev/null +++ b/docs/articles/council_files/girafe-binding-0.8.8/girafe.js @@ -0,0 +1,5 @@ +HTMLWidgets.widget({ + name: 'girafe', + type: 'output', + factory: ggiraphjs.factory(HTMLWidgets.shinyMode) +}); diff --git a/docs/articles/council_files/htmlwidgets-1.6.4/htmlwidgets.js b/docs/articles/council_files/htmlwidgets-1.6.4/htmlwidgets.js new file mode 100644 index 0000000..1067d02 --- /dev/null +++ b/docs/articles/council_files/htmlwidgets-1.6.4/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/articles/eurlexpkg.html b/docs/articles/eurlexpkg.html index be1689c..1c3d454 100644 --- a/docs/articles/eurlexpkg.html +++ b/docs/articles/eurlexpkg.html @@ -41,7 +41,7 @@ eurlex - 0.4.6 + 0.4.7
@@ -327,14 +327,14 @@

# elx_run_query()

 as_tibble(results)
-#> # A tibble: 4,429 × 3
+#> # A tibble: 4,449 × 3
 #>   work                                 type  celex     
 #>   <chr>                                <chr> <chr>     
 #> 1 469391ea-6c79-4680-84aa-c33db274e271 DIR   31979L0173
 #> 2 e8fcaf0d-443a-40ec-b778-34b7d895d334 DIR   31989L0194
 #> 3 52639f5f-ecaf-4f99-b633-e954cea5c8f3 DIR   31984L0378
 #> 4 c7560407-689b-4752-9fb0-d0624ed83a19 DIR   31966L0683
-#> # ℹ 4,425 more rows
+#> # ℹ 4,445 more rows

The function outputs a data.frame where each column corresponds to one of the requested variables, while the rows accumulate observations of the resource type satisfying the query criteria. diff --git a/docs/articles/eurlexpkg_files/figure-html/dirforce-1.png b/docs/articles/eurlexpkg_files/figure-html/dirforce-1.png index 57bdf91..35fe9db 100644 Binary files a/docs/articles/eurlexpkg_files/figure-html/dirforce-1.png and b/docs/articles/eurlexpkg_files/figure-html/dirforce-1.png differ diff --git a/docs/articles/eurlexpkg_files/figure-html/firstplot-1.png b/docs/articles/eurlexpkg_files/figure-html/firstplot-1.png index 52240f4..eaa272e 100644 Binary files a/docs/articles/eurlexpkg_files/figure-html/firstplot-1.png and b/docs/articles/eurlexpkg_files/figure-html/firstplot-1.png differ diff --git a/docs/articles/eurlexpkg_files/figure-html/wordcloud-1.png b/docs/articles/eurlexpkg_files/figure-html/wordcloud-1.png index aeb0c30..2a1c15e 100644 Binary files a/docs/articles/eurlexpkg_files/figure-html/wordcloud-1.png and b/docs/articles/eurlexpkg_files/figure-html/wordcloud-1.png differ diff --git a/docs/articles/index.html b/docs/articles/index.html index 21ce608..057556c 100644 --- a/docs/articles/index.html +++ b/docs/articles/index.html @@ -17,7 +17,7 @@ eurlex - 0.4.6 + 0.4.7 diff --git a/docs/articles/sparql-queries.html b/docs/articles/sparql-queries.html index f1ce905..4ccaece 100644 --- a/docs/articles/sparql-queries.html +++ b/docs/articles/sparql-queries.html @@ -40,7 +40,7 @@ eurlex - 0.4.6 + 0.4.7 diff --git a/docs/authors.html b/docs/authors.html index 04ac7d6..955cc61 100644 --- a/docs/authors.html +++ b/docs/authors.html @@ -17,7 +17,7 @@ eurlex - 0.4.6 + 0.4.7 diff --git a/docs/index.html b/docs/index.html index f67a5b2..25d65c3 100644 --- a/docs/index.html +++ b/docs/index.html @@ -40,7 +40,7 @@ eurlex - 0.4.6 + 0.4.7 diff --git a/docs/news/index.html b/docs/news/index.html index d66adf1..2dfa544 100644 --- a/docs/news/index.html +++ b/docs/news/index.html @@ -17,7 +17,7 @@ eurlex - 0.4.6 + 0.4.7 @@ -58,13 +58,22 @@

Changelog

- + +
+

Minor changes

+
+
+
+

Minor changes

diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index f4acf9a..a9f75f5 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -5,5 +5,5 @@ articles: council: council.html eurlexpkg: eurlexpkg.html sparql-queries: sparql-queries.html -last_built: 2023-09-06T16:51Z +last_built: 2024-02-25T20:10Z diff --git a/docs/reference/elx_council_votes.html b/docs/reference/elx_council_votes.html index a3766ec..1d975c1 100644 --- a/docs/reference/elx_council_votes.html +++ b/docs/reference/elx_council_votes.html @@ -17,7 +17,7 @@ eurlex - 0.4.6 + 0.4.7
diff --git a/docs/reference/elx_curia_list.html b/docs/reference/elx_curia_list.html index ffa0843..6c73853 100644 --- a/docs/reference/elx_curia_list.html +++ b/docs/reference/elx_curia_list.html @@ -18,7 +18,7 @@ eurlex - 0.4.6 + 0.4.7 @@ -95,20 +95,7 @@

Value

Examples

# \donttest{
 elx_curia_list(data = "cst_all", parse = FALSE)
-#> # A tibble: 1,759 × 3
-#>    case_id    case_id_celex case_info                                           
-#>    <chr>      <chr>         <chr>                                               
-#>  1 F-1/05 *   NA            Judgment of 26 October 2006, Landgren / ETF (F-1/05…
-#>  2 F-1/05     NA            Order of 22 May 2007, Landgren / ETF (F-1/05, ECR-S…
-#>  3 F-1/05 INT NA            Order of 13 July 2007, Landgren / ETF (F-1/05 INT, …
-#>  4 F-1/05     NA            Order of 9 November 2010, Landgren / ETF (F-1/05, u…
-#>  5 F-2/05     NA            Removed from the register on 18 June 2008, Kröppeli…
-#>  6 F-3/05     NA            Order of 15 May 2006, Schmit / Commission (F-3/05, …
-#>  7 F-4/05     NA            Removed from the register on 18 June 2008, Huober /…
-#>  8 F-5/05 *   NA            Judgment of 28 April 2009, Violetti and others / Co…
-#>  9 F-6/05     NA            Removed from the register on 18 June 2008, Kröppeli…
-#> 10 F-7/05     NA            Schmit / Commission (F-7/05) , see Case  F-5/05     
-#> # ℹ 1,749 more rows
+#> Error in rvest::html_text(hrefs, "href"): `trim` must be `TRUE` or `FALSE`, not the string "href".
 # }
 
diff --git a/docs/reference/elx_download_xml.html b/docs/reference/elx_download_xml.html index 7779e82..1be1dfa 100644 --- a/docs/reference/elx_download_xml.html +++ b/docs/reference/elx_download_xml.html @@ -17,7 +17,7 @@ eurlex - 0.4.6 + 0.4.7 diff --git a/docs/reference/elx_fetch_data.html b/docs/reference/elx_fetch_data.html index c7314b6..2feaf3a 100644 --- a/docs/reference/elx_fetch_data.html +++ b/docs/reference/elx_fetch_data.html @@ -17,7 +17,7 @@ eurlex - 0.4.6 + 0.4.7 diff --git a/docs/reference/elx_label_eurovoc.html b/docs/reference/elx_label_eurovoc.html index ec8a3a1..dc8b87a 100644 --- a/docs/reference/elx_label_eurovoc.html +++ b/docs/reference/elx_label_eurovoc.html @@ -17,7 +17,7 @@ eurlex - 0.4.6 + 0.4.7 diff --git a/docs/reference/elx_make_query.html b/docs/reference/elx_make_query.html index fb3288b..84309e7 100644 --- a/docs/reference/elx_make_query.html +++ b/docs/reference/elx_make_query.html @@ -19,7 +19,7 @@ eurlex - 0.4.6 + 0.4.7 diff --git a/docs/reference/elx_run_query.html b/docs/reference/elx_run_query.html index fa68caa..f2b765f 100644 --- a/docs/reference/elx_run_query.html +++ b/docs/reference/elx_run_query.html @@ -19,7 +19,7 @@ eurlex - 0.4.6 + 0.4.7 diff --git a/docs/reference/index.html b/docs/reference/index.html index bea4c72..d9e40dc 100644 --- a/docs/reference/index.html +++ b/docs/reference/index.html @@ -17,7 +17,7 @@ eurlex - 0.4.6 + 0.4.7 diff --git a/vignettes/articles/council.Rmd b/vignettes/articles/council.Rmd index bcc5259..c39a72b 100644 --- a/vignettes/articles/council.Rmd +++ b/vignettes/articles/council.Rmd @@ -8,9 +8,13 @@ vignette: > --- ```{r, echo = FALSE, message = FALSE, warning=FALSE, error=FALSE, include=FALSE} +# Sys.setenv(NOT_CRAN='true') +NOT_CRAN <- identical(tolower(Sys.getenv("NOT_CRAN")), "true") knitr::opts_chunk$set( collapse = TRUE, - comment = "#>" + comment = "#>", + purl = NOT_CRAN, + eval = NOT_CRAN ) ``` @@ -22,7 +26,7 @@ Under the OLP, which is nowadays the most common type of law-making procedure, t First we obtain the available data on Council votes using `eurlex::elx_council_votes()` and process the API response. -```{r votingdata} +```{r votingdata, eval=NOT_CRAN} # packages library(eurlex) library(ggplot2) @@ -72,7 +76,7 @@ To highlight that votes against tend to represent a strong signal of disagreemen The data on votes are easy enough to plot interactively with `ggplot2` and `ggiraph`. The following plot shows the variation in $p_i$ where $i$ indexes Member States. -```{r votesproportion} +```{r votesproportion, eval=NOT_CRAN} # viz Council votes and weighted proportion iplot_votes_prop <- country_votes_prop %>% mutate(tooltip = str_c(country,": ", prop, ". Total number of votes: ", n_votes)) %>% diff --git a/vignettes/articles/eurlexpkg.Rmd b/vignettes/articles/eurlexpkg.Rmd index 75e5f22..a90f9c8 100644 --- a/vignettes/articles/eurlexpkg.Rmd +++ b/vignettes/articles/eurlexpkg.Rmd @@ -11,7 +11,14 @@ vignette: > --- ```{r, echo = FALSE, message = FALSE, warning=FALSE, error=FALSE} -knitr::opts_chunk$set(collapse = T, comment = "#>") +# Sys.setenv(NOT_CRAN='true') +NOT_CRAN <- identical(tolower(Sys.getenv("NOT_CRAN")), "true") +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + purl = NOT_CRAN, + eval = NOT_CRAN +) options(tibble.print_min = 4, tibble.print_max = 4) ``` @@ -135,7 +142,7 @@ The data is returned in the long format, which means that rows are recycled up t EuroVoc is a multilingual thesaurus, keywords from which are used to describe the content of European Union documents. Most resource types that can be retrieved with the pre-defined queries in this package can be accompanied by EuroVoc keywords and these can be retrieved as other variables. -```{r eurovoc} +```{r eurovoc, eval = NOT_CRAN} rec_eurovoc <- elx_make_query("recommendation", include_eurovoc = TRUE, limit = 10) %>% elx_run_query() # truncated results for sake of the example @@ -147,7 +154,7 @@ rec_eurovoc %>% By default, the endpoint returns the EuroVoc concept codes rather than the labels (keywords). The function `elx_label_eurovoc()` needs to be called to obtain a look-up table with the labels. -```{r eurovoctable} +```{r eurovoctable, eval = NOT_CRAN} eurovoc_lookup <- elx_label_eurovoc(uri_eurovoc = rec_eurovoc$eurovoc) print(eurovoc_lookup) @@ -155,14 +162,14 @@ print(eurovoc_lookup) The results include labels only for unique identifiers, but with `dplyr::left_join()` it is straightforward to append the labels to the entire dataset. -```{r appendlabs} +```{r appendlabs, eval = NOT_CRAN} rec_eurovoc %>% left_join(eurovoc_lookup) ``` As elsewhere in the API, we can tap into the multilingual nature of EU documents also when it comes to the EuroVoc keywords. Moreover, most concepts in the thesaurus are associated with alternative labels; these can be returned as well (separated by a comma). -```{r} +```{r eurovoclookup, eval = NOT_CRAN} eurovoc_lookup <- elx_label_eurovoc(uri_eurovoc = rec_eurovoc$eurovoc, alt_labels = TRUE, language = "sk") @@ -178,7 +185,7 @@ A core contribution of the SPARQL requests is that we obtain a comprehensive lis One of the most sought-after data in the Eur-Lex dataverse is the text. It is possible now to automate the pipeline for downloading html and plain texts from Eur-Lex. Similarly, you can retrieve the title of the document. For both you can specify also the desired language (English by default). Other metadata might be added in the future. -```{r getdatapur, message = FALSE, warning=FALSE, error=FALSE} +```{r getdatapur, message = FALSE, warning=FALSE, error=FALSE, eval = NOT_CRAN} # the function is not vectorized by default # elx_fetch_data(url = results$work[1], type = "title") @@ -203,14 +210,14 @@ Note that text requests are by far the most time-intensive; requesting the full In this section I showcase a simple application of `eurlex` on making overviews of EU legislation. First, we collate data on directives. -```{r dirsdata, eval=FALSE} +```{r dirsdata, eval=FALSE, eval = NOT_CRAN} dirs <- elx_make_query(resource_type = "directive", include_date = TRUE, include_force = TRUE) %>% elx_run_query() ``` Let's calculate the proportion of directives currently in force in the entire set of directives ever adopted. This variable offers a particularly good demonstration of the usefulness of the package to retrieve EU law data, because it changes every day, as new acts enter into force and old ones drop out. Regularly scraping webpages for this purpose and scale is simply impractical and disproportional. -```{r firstplot, message = FALSE, warning=FALSE, error=FALSE} +```{r firstplot, message = FALSE, warning=FALSE, error=FALSE, eval = NOT_CRAN} library(ggplot2) dirs %>% @@ -221,7 +228,7 @@ dirs %>% Directives become naturally outdated with time. It might be all the more interesting to see which older acts are thus still surviving. -```{r dirforce} +```{r dirforce, eval = NOT_CRAN} dirs %>% filter(!is.na(force)) %>% mutate(date = as.Date(date)) %>% @@ -234,7 +241,7 @@ dirs %>% We want to know a bit more about some directives from the early 1970s that are still in force today. Their titles could give us a clue. -```{r dirtitles} +```{r dirtitles, eval = NOT_CRAN} dirs_1970_title <- dirs %>% filter(between(as.Date(date), as.Date("1970-01-01"), as.Date("1973-01-01")), force == "true") %>% @@ -248,7 +255,7 @@ print(dirs_1970_title) I will use the `tidytext` package to get a quick idea of what the legislation is about. -```{r wordcloud, message = FALSE, warning=FALSE, error=FALSE} +```{r wordcloud, message = FALSE, warning=FALSE, error=FALSE, eval = NOT_CRAN} library(tidytext) library(wordcloud)