|
(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; |
|
} |
|
|
|
// 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); |
|
} |
|
|
|
// 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; |
|
} |
|
} |
|
|
|
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%"; |
|
if (cel) { |
|
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.offsetWidth; }, |
|
getHeight: function() { return cel.offsetHeight; } |
|
}; |
|
|
|
} else { |
|
el.style.width = px(sizing.width); |
|
el.style.height = px(sizing.height); |
|
|
|
return { |
|
getWidth: function() { return el.offsetWidth; }, |
|
getHeight: function() { return el.offsetHeight; } |
|
}; |
|
} |
|
} |
|
|
|
// 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 = $("<span>").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 = $("<div>").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 |
|
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 as an output binding. |
|
|
|
// Merge defaults into the definition; don't mutate the original definition. |
|
// The base object is a Shiny output binding if we're running in Shiny mode, |
|
// or an empty object if we're not. |
|
var shinyBinding = extend(new Shiny.OutputBinding(), defaults, definition); |
|
|
|
// Wrap renderValue to handle initialization, which unfortunately isn't |
|
// supported natively by Shiny at the time of this writing. |
|
|
|
// NB: shinyBinding.initialize may be undefined, as it's optional. |
|
|
|
// Rename initialize to make sure it isn't called by a future version |
|
// of Shiny that does support initialize directly. |
|
shinyBinding._htmlwidgets_initialize = shinyBinding.initialize; |
|
delete shinyBinding.initialize; |
|
|
|
overrideMethod(shinyBinding, "find", function(superfunc) { |
|
return function(scope) { |
|
|
|
var results = superfunc(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; |
|
}; |
|
}); |
|
|
|
overrideMethod(shinyBinding, "renderValue", function(superfunc) { |
|
return function(el, data) { |
|
// Resolve strings marked as javascript literals to objects |
|
for (var i = 0; data.evals && i < data.evals.length; i++) { |
|
window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]); |
|
} |
|
if (!this.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 (this._htmlwidgets_initialize) { |
|
var result = this._htmlwidgets_initialize(el, el.offsetWidth, |
|
el.offsetHeight); |
|
elementData(el, "init_result", result); |
|
} |
|
} |
|
Shiny.renderDependencies(data.deps); |
|
superfunc(el, data.x, elementData(el, "init_result")); |
|
}; |
|
}); |
|
|
|
overrideMethod(shinyBinding, "resize", function(superfunc) { |
|
return 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")) { |
|
superfunc(el, width, height, elementData(el, "init_result")); |
|
} |
|
}; |
|
}); |
|
|
|
Shiny.outputBindings.register(shinyBinding, shinyBinding.name); |
|
} |
|
}; |
|
|
|
var scheduleStaticRenderTimerId = null; |
|
function scheduleStaticRender() { |
|
if (!scheduleStaticRenderTimerId) { |
|
scheduleStaticRenderTimerId = setTimeout(function() { |
|
scheduleStaticRenderTimerId = null; |
|
staticRender(); |
|
}, 1); |
|
} |
|
} |
|
|
|
// Render static widgets after the document finishes loading |
|
// Statically render all elements that are of this widget's class |
|
function staticRender() { |
|
var bindings = window.HTMLWidgets.widgets || []; |
|
for (var i = 0; i < bindings.length; i++) { |
|
var binding = bindings[i]; |
|
var matches = binding.find(document.documentElement); |
|
for (var j = 0; j < matches.length; j++) { |
|
var el = matches[j]; |
|
var sizeObj = initSizing(el, binding); |
|
|
|
if (hasClass(el, "html-widget-static-bound")) |
|
continue; |
|
el.className = el.className + " html-widget-static-bound"; |
|
|
|
var initResult; |
|
if (binding.initialize) { |
|
initResult = binding.initialize(el, |
|
sizeObj ? sizeObj.getWidth() : el.offsetWidth, |
|
sizeObj ? sizeObj.getHeight() : el.offsetHeight |
|
); |
|
} |
|
|
|
if (binding.resize) { |
|
var lastSize = {}; |
|
on(window, "resize", function(e) { |
|
var size = { |
|
w: sizeObj ? sizeObj.getWidth() : el.offsetWidth, |
|
h: sizeObj ? sizeObj.getHeight() : el.offsetHeight |
|
}; |
|
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); |
|
}); |
|
} |
|
|
|
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 |
|
for (var i = 0; data.evals && i < data.evals.length; i++) { |
|
window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]); |
|
} |
|
binding.renderValue(el, data.x, initResult); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Wait until after the document has loaded to render the widgets. |
|
if (document.addEventListener) { |
|
document.addEventListener("DOMContentLoaded", function() { |
|
document.removeEventListener("DOMContentLoaded", arguments.callee, false); |
|
staticRender(); |
|
}, false); |
|
} else if (document.attachEvent) { |
|
document.attachEvent("onreadystatechange", function() { |
|
if (document.readyState === "complete") { |
|
document.detachEvent("onreadystatechange", arguments.callee); |
|
staticRender(); |
|
} |
|
}); |
|
} |
|
|
|
|
|
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) { |
|
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] = eval("(" + o[part] + ")"); |
|
} else { // otherwise continue to next embedded object |
|
o = o[part]; |
|
} |
|
} |
|
} |
|
}; |
|
})(); |