diff --git a/R/app_ui.R b/R/app_ui.R index d8ad2d7d..775316cf 100644 --- a/R/app_ui.R +++ b/R/app_ui.R @@ -11,7 +11,13 @@ app_ui <- function(meta, domainData, mapping, standards){ #read css from pacakge - app_css <- HTML(readLines( paste(.libPaths(),'safetyGraphics','safetyGraphics_app', 'www','index.css', sep="/"))) + app_css <- NULL + for(lib in .libPaths()){ + if(is.null(app_css)){ + css_path <- paste(lib,'safetyGraphics','safetyGraphics_app', 'www','index.css', sep="/") + if(file.exists(css_path)) app_css <- HTML(readLines(css_path)) + } + } #script to append population badge nav bar participant_badge<-tags$script( diff --git a/R/makeChartConfig.R b/R/makeChartConfig.R index f1bbfd71..644c369e 100644 --- a/R/makeChartConfig.R +++ b/R/makeChartConfig.R @@ -23,8 +23,21 @@ makeChartConfig <- function(dirs, sourceFiles=TRUE){ # Use the charts settings saved in safetycharts if no path is provided. if(missing(dirs) || is.null(dirs)){ - #dirs<-paste(.libPaths(),'safetycharts','chartSettings', sep="/") - dirs<-paste(.libPaths(),'safetycharts','config', sep="/") + safetyChartsFound<-FALSE + for(lib in .libPaths()){ + print(lib) + dirs<-paste(lib,'safetyCharts','config', sep="/") + if(file.exists(dirs)) { + print("found configs") + print(dirs) + safetyChartsFound<-TRUE + break + } + } + + if(!safetyChartsFound){ + message("safetyCharts library not found, please install safetyCharts or provide a path to custom chart configuration files. See safetyGraphics vignettes for details.") + } } if(sourceFiles){ @@ -54,12 +67,17 @@ makeChartConfig <- function(dirs, sourceFiles=TRUE){ chart <- read_yaml(path) chart$path <- path chart$name <- path %>% file_path_sans_ext %>% basename + chart$order <- ifelse( + is.null(chart$order), + length(yaml_files) + 1, + chart$order + ) %>% as.numeric + return(chart) }) - - names(charts) <- yaml_files %>% file_path_sans_ext %>% basename + charts <- charts[order(purrr::map_dbl(charts, function(chart) chart$order))] message("Found ", length(yaml_files), " config files: ",paste(names(charts),collapse=", ")) @@ -96,6 +114,5 @@ makeChartConfig <- function(dirs, sourceFiles=TRUE){ return(chart) } ) - return(charts) } diff --git a/R/mod_chartsTab.R b/R/mod_chartsTab.R index 873ba17d..10c33ba0 100644 --- a/R/mod_chartsTab.R +++ b/R/mod_chartsTab.R @@ -57,7 +57,6 @@ chartsTab <- function(input, output, session, chart, data, mapping){ message(chart$name, " has an init.") print(chart$functions[chart$workflow$init]) params <- do.call(chart$functions[[chart$workflow$init]], params) - print(params) } } return(params) @@ -94,4 +93,4 @@ chartsTab <- function(input, output, session, chart, data, mapping){ type=chart$type ) } -} \ No newline at end of file +} diff --git a/R/mod_filterTab.R b/R/mod_filterTab.R index 83040daf..f169abb3 100644 --- a/R/mod_filterTab.R +++ b/R/mod_filterTab.R @@ -26,7 +26,7 @@ filterTabUI <- function(id, filterDomain = "dm"){ id = ns("pbar"), value = 100, total = 100, display_pct = TRUE ), - shiny::dataTableOutput(outputId = ns("table")), + DT::dataTableOutput(outputId = ns("table")), tags$p("Code dplyr:"), verbatimTextOutput(outputId = ns("code_dplyr")), tags$p("Expression:"), @@ -80,7 +80,7 @@ filterTab <- function(input, output, session, domainData, filterDomain, id_col){ ) }) - output$table <- shiny::renderDataTable({ + output$table <- DT::renderDataTable({ res_filter$data_filtered() }, options = list(pageLength = 5)) diff --git a/R/mod_mappingColumn.R b/R/mod_mappingColumn.R index 15339602..42800820 100644 --- a/R/mod_mappingColumn.R +++ b/R/mod_mappingColumn.R @@ -109,7 +109,7 @@ mappingColumn <- function(input, output, session, meta, data){ # return the values for all fields as a data.frame meta <- reactive({ - col_meta <- data.frame(text_key = col_meta$text_key, current=col_val()) + col_meta <- data.frame(text_key = col_meta$text_key, current=col_val(), stringsAsFactors = FALSE) if(nrow(field_meta)>0){ for(field_id in field_ids){ field_meta <- data.frame(text_key = field_id, current=field_vals[[field_id]]()) diff --git a/inst/htmlwidgets/chartRenderer.js b/inst/htmlwidgets/chartRenderer.js deleted file mode 100644 index 55811967..00000000 --- a/inst/htmlwidgets/chartRenderer.js +++ /dev/null @@ -1,38 +0,0 @@ -HTMLWidgets.widget({ - - name: "chartRenderer", - - type: "output", - - factory: function(el, width, height) { - - // TODO: define shared variables for this instance - - return { - - renderValue: function(rSettings) { - el.innerHTML = "
"; - var settings = rSettings.settings; - - if(settings.debug_js){ - console.log("R settings:") - console.log(rSettings); - } - console.log(rSettings); - rSettings.data = HTMLWidgets.dataframeToD3(rSettings.data); - if(rSettings.subFunction){ - var chart = window[rSettings.chartFunction][rSettings.subFunction]("." + rSettings.chartFunction + "-wrapper", settings) - }else{ - var chart = window[rSettings.chartFunction]("." + rSettings.chartFunction + "-wrapper", settings) - } - - chart.init(rSettings.data); - }, - - resize: function(width, height) { - // TODO: code to re-render the widget with a new size - } - - }; - } -}); diff --git a/inst/htmlwidgets/chartRenderer.yaml b/inst/htmlwidgets/chartRenderer.yaml deleted file mode 100644 index 56b65b05..00000000 --- a/inst/htmlwidgets/chartRenderer.yaml +++ /dev/null @@ -1,47 +0,0 @@ -dependencies: - - name: d3 - version: 3.5.17 - src: htmlwidgets/lib/d3-3.5.17 - script: d3.v3.min.js - - name: webcharts - version: 1.11.6 - src: htmlwidgets/lib/webcharts-1.11.6 - script: webcharts.js - stylesheet: webcharts.css - - name: safety-eDish - version: 1.2.0 - src: htmlwidgets/lib/hep-explorer-1.3.1 - script: hepexplorer.js - - name: safety-histogram - version: 2.4.1 - src: htmlwidgets/lib/safety-histogram-2.4.1 - script: safetyHistogram.js - - name: safety-outlier-explorer - version: 2.6.0 - src: htmlwidgets/lib/safety-outlier-explorer-2.6.0 - script: safetyOutlierExplorer.js - - name: safety-shift-plot - version: 2.1.3 - src: htmlwidgets/lib/safety-shift-plot-2.1.3 - script: safetyShiftPlot.js - - name: safety-results-over-time - version: 2.3.3 - src: htmlwidgets/lib/safety-results-over-time-2.3.3 - script: safetyResultsOverTime.js - - name: paneled-outlier-explorer - version: 1.1.4 - src: htmlwidgets/lib/paneled-outlier-explorer-1.1.4 - script: paneledOutlierExplorer.js - - name: safety-delta-delta - version: 1.0.0 - src: htmlwidgets/lib/safety-delta-delta-1.0.0 - script: safetyDeltaDelta.js - - name: ae-timelines - version: 2.1.6 - src: htmlwidgets/lib/ae-timelines-2.1.6 - script: aeTimelines.js - - name: aeexplorer - version: 1.11.6 - src: htmlwidgets/lib/aeexplorer-3.3.5 - script: aeTable.js - stylesheet: aeTable.css diff --git a/inst/htmlwidgets/hepexplorer.js b/inst/htmlwidgets/hepexplorer.js deleted file mode 100644 index c148d360..00000000 --- a/inst/htmlwidgets/hepexplorer.js +++ /dev/null @@ -1,26 +0,0 @@ -HTMLWidgets.widget({ - name: "hepexplorer", - type: "output", - - factory: function(el, width, height) { - - return { - renderValue: function(rSettings) { - console.log("widget started ...") - console.log(el) - console.log(rSettings) - //console.log(rSettings) - //el.innerHTML = ""; - el.innerHTML="" - let settings = rSettings.settings; - let data = HTMLWidgets.dataframeToD3(rSettings.data); - var chart = hepexplorer("#"+rSettings.ns, settings) - chart.init(data); - }, - resize: function(width, height) { - // TODO: code to re-render the widget with a new size - } - - }; - } -}); diff --git a/inst/htmlwidgets/hepexplorer.yaml b/inst/htmlwidgets/hepexplorer.yaml deleted file mode 100644 index e78f670a..00000000 --- a/inst/htmlwidgets/hepexplorer.yaml +++ /dev/null @@ -1,14 +0,0 @@ -dependencies: - - name: d3 - version: 3.5.17 - src: htmlwidgets/lib/d3-3.5.17 - script: d3.v3.min.js - - name: webcharts - version: 1.11.6 - src: htmlwidgets/lib/webcharts-1.11.6 - script: webcharts.js - stylesheet: webcharts.css - - name: safety-eDish - version: 1.3.1 - src: htmlwidgets/lib/hep-explorer-1.3.1 - script: hepexplorer.js diff --git a/inst/htmlwidgets/lib/ae-timelines-2.1.6/aeTimelines.js b/inst/htmlwidgets/lib/ae-timelines-2.1.6/aeTimelines.js deleted file mode 100644 index cfc093f2..00000000 --- a/inst/htmlwidgets/lib/ae-timelines-2.1.6/aeTimelines.js +++ /dev/null @@ -1,1158 +0,0 @@ -(function(global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' - ? (module.exports = factory(require('d3'), require('webcharts'))) - : typeof define === 'function' && define.amd - ? define(['d3', 'webcharts'], factory) - : (global.aeTimelines = factory(global.d3, global.webCharts)); -})(this, function(d3, webcharts) { - 'use strict'; - - if (typeof Object.assign != 'function') { - Object.defineProperty(Object, 'assign', { - value: function assign(target, varArgs) { - if (target == null) { - // TypeError if undefined or null - throw new TypeError('Cannot convert undefined or null to object'); - } - - var to = Object(target); - - for (var index = 1; index < arguments.length; index++) { - var nextSource = arguments[index]; - - if (nextSource != null) { - // Skip over if undefined or null - for (var nextKey in nextSource) { - // Avoid bugs when hasOwnProperty is shadowed - if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { - to[nextKey] = nextSource[nextKey]; - } - } - } - } - - return to; - }, - writable: true, - configurable: true - }); - } - - if (!Array.prototype.find) { - Object.defineProperty(Array.prototype, 'find', { - value: function value(predicate) { - // 1. Let O be ? ToObject(this value). - if (this == null) { - throw new TypeError('"this" is null or not defined'); - } - - var o = Object(this); - - // 2. Let len be ? ToLength(? Get(O, 'length')). - var len = o.length >>> 0; - - // 3. If IsCallable(predicate) is false, throw a TypeError exception. - if (typeof predicate !== 'function') { - throw new TypeError('predicate must be a function'); - } - - // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. - var thisArg = arguments[1]; - - // 5. Let k be 0. - var k = 0; - - // 6. Repeat, while k < len - while (k < len) { - // a. Let Pk be ! ToString(k). - // b. Let kValue be ? Get(O, Pk). - // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)). - // d. If testResult is true, return kValue. - var kValue = o[k]; - if (predicate.call(thisArg, kValue, k, o)) { - return kValue; - } - // e. Increase k by 1. - k++; - } - - // 7. Return undefined. - return undefined; - } - }); - } - - if (!Array.prototype.findIndex) { - Object.defineProperty(Array.prototype, 'findIndex', { - value: function value(predicate) { - // 1. Let O be ? ToObject(this value). - if (this == null) { - throw new TypeError('"this" is null or not defined'); - } - - var o = Object(this); - - // 2. Let len be ? ToLength(? Get(O, "length")). - var len = o.length >>> 0; - - // 3. If IsCallable(predicate) is false, throw a TypeError exception. - if (typeof predicate !== 'function') { - throw new TypeError('predicate must be a function'); - } - - // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. - var thisArg = arguments[1]; - - // 5. Let k be 0. - var k = 0; - - // 6. Repeat, while k < len - while (k < len) { - // a. Let Pk be ! ToString(k). - // b. Let kValue be ? Get(O, Pk). - // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)). - // d. If testResult is true, return k. - var kValue = o[k]; - if (predicate.call(thisArg, kValue, k, o)) { - return k; - } - // e. Increase k by 1. - k++; - } - - // 7. Return -1. - return -1; - } - }); - } - - var _typeof = - typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol' - ? function(obj) { - return typeof obj; - } - : function(obj) { - return obj && - typeof Symbol === 'function' && - obj.constructor === Symbol && - obj !== Symbol.prototype - ? 'symbol' - : typeof obj; - }; - - /*------------------------------------------------------------------------------------------------\ - Clone a variable (http://stackoverflow.com/a/728694). - \------------------------------------------------------------------------------------------------*/ - - function clone(obj) { - var copy; - - //Handle the 3 simple types, and null or undefined - if (null == obj || 'object' != (typeof obj === 'undefined' ? 'undefined' : _typeof(obj))) - return obj; - - //Handle Date - if (obj instanceof Date) { - copy = new Date(); - copy.setTime(obj.getTime()); - return copy; - } - - //Handle Array - if (obj instanceof Array) { - copy = []; - for (var i = 0, len = obj.length; i < len; i++) { - copy[i] = clone(obj[i]); - } - return copy; - } - - //Handle Object - if (obj instanceof Object) { - copy = {}; - for (var attr in obj) { - if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]); - } - return copy; - } - - throw new Error("Unable to copy obj! Its type isn't supported."); - } - - var isMergeableObject = function isMergeableObject(value) { - return isNonNullObject(value) && !isSpecial(value); - }; - - function isNonNullObject(value) { - return ( - !!value && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' - ); - } - - function isSpecial(value) { - var stringValue = Object.prototype.toString.call(value); - - return ( - stringValue === '[object RegExp]' || - stringValue === '[object Date]' || - isReactElement(value) - ); - } - - // see https://github.com/facebook/react/blob/b5ac963fb791d1298e7f396236383bc955f916c1/src/isomorphic/classic/element/ReactElement.js#L21-L25 - var canUseSymbol = typeof Symbol === 'function' && Symbol.for; - var REACT_ELEMENT_TYPE = canUseSymbol ? Symbol.for('react.element') : 0xeac7; - - function isReactElement(value) { - return value.$$typeof === REACT_ELEMENT_TYPE; - } - - function emptyTarget(val) { - return Array.isArray(val) ? [] : {}; - } - - function cloneUnlessOtherwiseSpecified(value, options) { - return options.clone !== false && options.isMergeableObject(value) - ? deepmerge(emptyTarget(value), value, options) - : value; - } - - function defaultArrayMerge(target, source, options) { - return target.concat(source).map(function(element) { - return cloneUnlessOtherwiseSpecified(element, options); - }); - } - - function mergeObject(target, source, options) { - var destination = {}; - - if (options.isMergeableObject(target)) { - Object.keys(target).forEach(function(key) { - destination[key] = cloneUnlessOtherwiseSpecified(target[key], options); - }); - } - - Object.keys(source).forEach(function(key) { - if (!options.isMergeableObject(source[key]) || !target[key]) { - destination[key] = cloneUnlessOtherwiseSpecified(source[key], options); - } else { - destination[key] = deepmerge(target[key], source[key], options); - } - }); - - return destination; - } - - function deepmerge(target, source, options) { - options = options || {}; - - options.arrayMerge = options.arrayMerge || defaultArrayMerge; - - options.isMergeableObject = options.isMergeableObject || isMergeableObject; - - var sourceIsArray = Array.isArray(source); - - var targetIsArray = Array.isArray(target); - - var sourceAndTargetTypesMatch = sourceIsArray === targetIsArray; - - if (!sourceAndTargetTypesMatch) { - return cloneUnlessOtherwiseSpecified(source, options); - } else if (sourceIsArray) { - return options.arrayMerge(target, source, options); - } else { - return mergeObject(target, source, options); - } - } - - deepmerge.all = function deepmergeAll(array, options) { - if (!Array.isArray(array)) { - throw new Error('first argument should be an array'); - } - - return array.reduce(function(prev, next) { - return deepmerge(prev, next, options); - }, {}); - }; - - var deepmerge_1 = deepmerge; - - var rendererSpecificSettings = { - id_col: 'USUBJID', - seq_col: 'AESEQ', - stdy_col: 'ASTDY', - endy_col: 'AENDY', - term_col: 'AETERM', - - color: { - value_col: 'AESEV', - label: 'Severity/Intensity', - values: ['MILD', 'MODERATE', 'SEVERE'], - colors: [ - '#66bd63', // mild - '#fdae61', // moderate - '#d73027', // severe - '#377eb8', - '#984ea3', - '#ff7f00', - '#a65628', - '#f781bf' - ] - }, - - highlight: { - value_col: 'AESER', - label: 'Serious Event', - value: 'Y', - detail_col: null, - attributes: { - stroke: 'black', - 'stroke-width': '2', - fill: 'none' - } - }, - - filters: null, - details: null, - custom_marks: null - }; - - var webchartsSettings = { - x: { - column: 'wc_value', - type: 'linear', - label: null - }, - y: { - column: null, // set in syncSettings() - type: 'ordinal', - label: '', - sort: 'earliest', - behavior: 'flex' - }, - marks: [ - { - type: 'line', - per: null, // set in syncSettings() - tooltip: null, // set in syncSettings() - attributes: { - 'stroke-width': 5, - 'stroke-opacity': 0.5 - } - }, - { - type: 'circle', - per: null, // set in syncSettings() - tooltip: null, // set in syncSettings() - attributes: { - 'fill-opacity': 0.5, - 'stroke-opacity': 0.5 - } - } - ], - legend: { location: 'top', mark: 'circle' }, - gridlines: 'y', - range_band: 15, - margin: { top: 50 }, // for second x-axis - resizable: true - }; - - var defaultSettings = Object.assign({}, rendererSpecificSettings, webchartsSettings); - - function syncSettings(preSettings) { - var nextSettings = clone(preSettings); - - nextSettings.y.column = nextSettings.id_col; - - //Lines (AE duration) - nextSettings.marks[0].per = [nextSettings.id_col, nextSettings.seq_col]; - nextSettings.marks[0].tooltip = - 'Reported Term: [' + - nextSettings.term_col + - ']' + - ('\nStart Day: [' + nextSettings.stdy_col + ']') + - ('\nStop Day: [' + nextSettings.endy_col + ']'); - - //Circles (AE start day) - nextSettings.marks[1].per = [nextSettings.id_col, nextSettings.seq_col, 'wc_value']; - nextSettings.marks[1].tooltip = - 'Reported Term: [' + - nextSettings.term_col + - ']' + - ('\nStart Day: [' + nextSettings.stdy_col + ']') + - ('\nStop Day: [' + nextSettings.endy_col + ']'); - nextSettings.marks[1].values = { wc_category: [nextSettings.stdy_col] }; - - //Define highlight marks. - if (nextSettings.highlight) { - //Lines (highlighted event duration) - var highlightLine = { - type: 'line', - per: [nextSettings.id_col, nextSettings.seq_col], - tooltip: - 'Reported Term: [' + - nextSettings.term_col + - ']' + - ('\nStart Day: [' + nextSettings.stdy_col + ']') + - ('\nStop Day: [' + nextSettings.endy_col + ']') + - ('\n' + - nextSettings.highlight.label + - ': [' + - (nextSettings.highlight.detail_col - ? nextSettings.highlight.detail_col - : nextSettings.highlight.value_col) + - ']'), - values: {}, - attributes: nextSettings.highlight.attributes || {} - }; - highlightLine.values[nextSettings.highlight.value_col] = nextSettings.highlight.value; - highlightLine.attributes.class = 'highlight'; - nextSettings.marks.push(highlightLine); - - //Circles (highlighted event start day) - var highlightCircle = { - type: 'circle', - per: [nextSettings.id_col, nextSettings.seq_col, 'wc_value'], - tooltip: - 'Reported Term: [' + - nextSettings.term_col + - ']' + - ('\nStart Day: [' + nextSettings.stdy_col + ']') + - ('\nStop Day: [' + nextSettings.endy_col + ']') + - ('\n' + - nextSettings.highlight.label + - ': [' + - (nextSettings.highlight.detail_col - ? nextSettings.highlight.detail_col - : nextSettings.highlight.value_col) + - ']'), - values: { wc_category: nextSettings.stdy_col }, - attributes: nextSettings.highlight.attributes || {} - }; - highlightCircle.values[nextSettings.highlight.value_col] = nextSettings.highlight.value; - highlightCircle.attributes.class = 'highlight'; - nextSettings.marks.push(highlightCircle); - } - - //Define mark coloring and legend. - nextSettings.color_by = nextSettings.color.value_col; - nextSettings.colors = nextSettings.color.colors; - nextSettings.legend = nextSettings.legend || { location: 'top' }; - nextSettings.legend.label = nextSettings.color.label; - nextSettings.legend.order = nextSettings.color.values; - nextSettings.color_dom = nextSettings.color.values; - - //Default filters - if (!nextSettings.filters || nextSettings.filters.length === 0) { - nextSettings.filters = [ - { value_col: nextSettings.color.value_col, label: nextSettings.color.label }, - { value_col: nextSettings.id_col, label: 'Participant Identifier' } - ]; - if (nextSettings.highlight) - nextSettings.filters.unshift({ - value_col: nextSettings.highlight.value_col, - label: nextSettings.highlight.label - }); - } - - //Default detail listing columns - var defaultDetails = [ - { value_col: nextSettings.seq_col, label: 'Sequence Number' }, - { value_col: nextSettings.stdy_col, label: 'Start Day' }, - { value_col: nextSettings.endy_col, label: 'Stop Day' }, - { value_col: nextSettings.term_col, label: 'Reported Term' } - ]; - - //Add settings.color.value_col to default details. - defaultDetails.push({ - value_col: nextSettings.color.value_col, - label: nextSettings.color.label - }); - - //Add settings.highlight.value_col and settings.highlight.detail_col to default details. - if (nextSettings.highlight) { - defaultDetails.push({ - value_col: nextSettings.highlight.value_col, - label: nextSettings.highlight.label - }); - - if (nextSettings.highlight.detail_col) - defaultDetails.push({ - value_col: nextSettings.highlight.detail_col, - label: nextSettings.highlight.label + ' Details' - }); - } - - //Add settings.filters columns to default details. - nextSettings.filters.forEach(function(filter) { - if (filter !== nextSettings.id_col && filter.value_col !== nextSettings.id_col) - defaultDetails.push({ - value_col: filter.value_col, - label: filter.label - }); - }); - - //Redefine settings.details with defaults. - if (!nextSettings.details) nextSettings.details = defaultDetails; - else { - //Allow user to specify an array of columns or an array of objects with a column property - //and optionally a column label. - nextSettings.details = nextSettings.details.map(function(d) { - return { - value_col: d.value_col ? d.value_col : d, - label: d.label ? d.label : d.value_col ? d.value_col : d - }; - }); - - //Add default details to settings.details. - defaultDetails.reverse().forEach(function(defaultDetail) { - return nextSettings.details.unshift(defaultDetail); - }); - } - - //Add custom marks to marks array. - if (nextSettings.custom_marks) - nextSettings.custom_marks.forEach(function(custom_mark) { - custom_mark.attributes = custom_mark.attributes || {}; - custom_mark.attributes.class = 'custom'; - nextSettings.marks.push(custom_mark); - }); - - return nextSettings; - } - - var controlInputs = [ - { - type: 'dropdown', - option: 'y.sort', - label: 'Sort Participant IDs', - values: ['earliest', 'alphabetical-descending'], - require: true - } - ]; - - function syncControlInputs(preControlInputs, preSettings) { - preSettings.filters.forEach(function(d, i) { - var thisFilter = { - type: 'subsetter', - value_col: d.value_col ? d.value_col : d, - label: d.label ? d.label : d.value_col ? d.value_col : d - }; - //add the filter to the control inputs (as long as it isn't already there) - var current_value_cols = preControlInputs - .filter(function(f) { - return f.type == 'subsetter'; - }) - .map(function(m) { - return m.value_col; - }); - if (current_value_cols.indexOf(thisFilter.value_col) == -1) - preControlInputs.unshift(thisFilter); - }); - - return preControlInputs; - } - - function syncSecondSettings(preSettings) { - var nextSettings = clone(preSettings); - - nextSettings.y.column = nextSettings.seq_col; - nextSettings.y.sort = 'alphabetical-descending'; - - nextSettings.marks[0].per = [nextSettings.seq_col]; - nextSettings.marks[1].per = [nextSettings.seq_col, 'wc_value']; - - if (nextSettings.highlight) { - nextSettings.marks[2].per = [nextSettings.seq_col]; - nextSettings.marks[3].per = [nextSettings.seq_col, 'wc_value']; - } - - nextSettings.range_band = preSettings.range_band * 2; - nextSettings.margin = null; - nextSettings.transitions = false; - - return nextSettings; - } - - function calculatePopulationSize() { - var _this = this; - - this.populationCount = d3 - .set( - this.raw_data.map(function(d) { - return d[_this.config.id_col]; - }) - ) - .values().length; - } - - function cleanData() { - var _this = this; - - this.superRaw = this.raw_data; - var N = this.superRaw.length; - - //Remove records with empty verbatim terms. - this.superRaw = this.superRaw.filter(function(d) { - return /[^\s*$]/.test(d[_this.config.term_col]); - }); - var n1 = this.superRaw.length; - var diff1 = N - n1; - if (diff1) - console.warn(diff1 + ' records without [ ' + this.config.term_col + ' ] removed.'); - - //Remove records with non-integer start days. - this.superRaw = this.superRaw.filter(function(d) { - return /^-?\d+$/.test(d[_this.config.stdy_col]); - }); - var n2 = this.superRaw.length; - var diff2 = n1 - n2; - if (diff2) - console.warn(diff2 + ' records without [ ' + this.config.stdy_col + ' ] removed.'); - } - - function checkFilters() { - var _this = this; - - this.controls.config.inputs = this.controls.config.inputs.filter(function(input) { - if (input.type !== 'subsetter') return true; - else { - var levels = d3 - .set( - _this.superRaw.map(function(d) { - return d[input.value_col]; - }) - ) - .values(); - if (levels.length < 2) { - console.warn( - 'The [ ' + - input.value_col + - ' ] filter was removed because the variable has only one level.' - ); - return false; - } - - return true; - } - }); - } - - function checkColorBy() { - var _this = this; - - this.superRaw.forEach(function(d) { - return (d[_this.config.color_by] = /[^\s*$]/.test(d[_this.config.color_by]) - ? d[_this.config.color_by] - : 'N/A'); - }); - - //Flag NAs - if ( - this.superRaw.some(function(d) { - return d[_this.config.color_by] === 'N/A'; - }) - ) - this.na = true; - } - - function defineColorDomain() { - var _this = this; - - var color_by_values = d3 - .set( - this.superRaw.map(function(d) { - return d[_this.config.color_by]; - }) - ) - .values() - .sort(function(a, b) { - var aIndex = _this.config.color.values.indexOf(a); - var bIndex = _this.config.color.values.indexOf(b); - var diff = aIndex > -1 && bIndex > -1 ? aIndex - bIndex : 0; - - return diff - ? diff - : aIndex > -1 - ? -1 - : bIndex > -1 - ? 1 - : a === 'N/A' - ? 1 - : b === 'N/A' ? -1 : a.toLowerCase() < b.toLowerCase() ? -1 : 1; - }); - color_by_values.forEach(function(color_by_value, i) { - if (_this.config.color.values.indexOf(color_by_value) < 0) { - _this.config.color_dom.push(color_by_value); - _this.config.legend.order.push(color_by_value); - _this.chart2.config.color_dom.push(color_by_value); - _this.chart2.config.legend.order.push(color_by_value); - } - }); - } - - /*------------------------------------------------------------------------------------------------\ - Expand a data array to one item per original item per specified column. - \------------------------------------------------------------------------------------------------*/ - - function lengthenRaw() { - var data = this.superRaw; - var columns = [this.config.stdy_col, this.config.endy_col]; - var my_data = []; - - data.forEach(function(d) { - columns.forEach(function(column) { - var obj = Object.assign({}, d); - obj.wc_category = column; - obj.wc_value = parseFloat(d[column]); - my_data.push(obj); - }); - }); - - this.raw_data = my_data; - } - - function initCustomEvents() { - var chart = this; - chart.participantsSelected = []; - chart.events.participantsSelected = new CustomEvent('participantsSelected'); - } - - function onInit() { - calculatePopulationSize.call(this); - cleanData.call(this); - checkFilters.call(this); - checkColorBy.call(this); - defineColorDomain.call(this); - lengthenRaw.call(this); - initCustomEvents.call(this); - } - - function sortLegendFilter() { - var _this = this; - - this.controls.wrap - .selectAll('.control-group') - .filter(function(d) { - return d.value_col === _this.config.color.value_col; - }) - .selectAll('option') - .sort(function(a, b) { - return _this.config.legend.order.indexOf(a) - _this.config.legend.order.indexOf(b); - }); - } - - function addParticipantCountContainer() { - this.wrap - .select('.legend') - .append('span') - .classed('annote', true) - .style('float', 'right') - .style('font-style', 'italic'); - } - - function addTopXaxis() { - this.svg - .append('g') - .attr('class', 'x2 axis linear') - .append('text') - .attr({ - class: 'axis-title top', - dy: '2em', - 'text-anchor': 'middle' - }) - .text(this.config.x_label); - } - - function addBackButton() { - var _this = this; - - this.chart2.wrap - .insert('div', ':first-child') - .attr('id', 'backButton') - .insert('button', '.legend') - .html('← Back') - .style('cursor', 'pointer') - .on('click', function() { - //Trigger participantsSelected event - _this.participantsSelected = []; - _this.events.participantsSelected.data = _this.participantsSelected; - _this.wrap.node().dispatchEvent(_this.events.participantsSelected); - - //remove the details chart - _this.chart2.wrap.select('.id-title').remove(); - _this.chart2.wrap.style('display', 'none'); - _this.table.wrap.style('display', 'none'); - _this.controls.wrap.style('display', 'block'); - _this.wrap.style('display', 'block'); - _this.draw(); - }); - } - - function onLayout() { - sortLegendFilter.call(this); - addParticipantCountContainer.call(this); - addTopXaxis.call(this); - addBackButton.call(this); - } - - function onPreprocess() {} - - function onDatatransform() {} - - function addNAToColorScale() { - if (this.na) - // defined in ../onInit/checkColorBy - this.colorScale.range().splice(this.colorScale.domain().indexOf('N/A'), 1, '#999999'); - } - - /*------------------------------------------------------------------------------------------------\ - Annotate number of participants based on current filters, number of participants in all, and - the corresponding percentage. - - Inputs: - - chart - a webcharts chart object - id_unit - a text string to label the units in the annotation (default = 'participants') - selector - css selector for the annotation - \------------------------------------------------------------------------------------------------*/ - - function updateParticipantCount(chart, selector, id_unit) { - //count the number of unique ids in the current chart and calculate the percentage - var filtered_data = chart.raw_data.filter(function(d) { - var filtered = d[chart.config.seq_col] === ''; - chart.filters.forEach(function(di) { - if (filtered === false && di.val !== 'All') - filtered = - Object.prototype.toString.call(di.val) === '[object Array]' - ? di.val.indexOf(d[di.col]) === -1 - : di.val !== d[di.col]; - }); - return !filtered; - }); - var currentObs = d3 - .set( - filtered_data.map(function(d) { - return d[chart.config.id_col]; - }) - ) - .values().length; - - var percentage = d3.format('0.1%')(currentObs / chart.populationCount); - - //clear the annotation - var annotation = d3.select(selector); - annotation.selectAll('*').remove(); - - //update the annotation - var units = id_unit ? ' ' + id_unit : ' participant(s)'; - annotation.text( - currentObs + ' of ' + chart.populationCount + units + ' shown (' + percentage + ')' - ); - } - - function sortYdomain() { - var _this = this; - - var yAxisSort = this.controls.wrap - .selectAll('.control-group') - .filter(function(d) { - return d.option && d.option === 'y.sort'; - }) - .select('option:checked') - .text(); - - if (yAxisSort === 'earliest') { - //Redefine filtered data as it defaults to the final mark drawn, which might be filtered in - //addition to the current filter selections. - var filtered_data = this.raw_data.filter(function(d) { - var filtered = d[_this.config.seq_col] === ''; - _this.filters.forEach(function(di) { - if (filtered === false && di.val !== 'All') - filtered = - Object.prototype.toString.call(di.val) === '[object Array]' - ? di.val.indexOf(d[di.col]) === -1 - : di.val !== d[di.col]; - }); - return !filtered; - }); - - //Capture all participant IDs with adverse events with a start day. - var withStartDay = d3 - .nest() - .key(function(d) { - return d[_this.config.id_col]; - }) - .rollup(function(d) { - return d3.min(d, function(di) { - return +di[_this.config.stdy_col]; - }); - }) - .entries( - filtered_data.filter(function(d) { - return ( - !isNaN(parseFloat(d[_this.config.stdy_col])) && - isFinite(d[_this.config.stdy_col]) - ); - }) - ) - .sort(function(a, b) { - return a.values > b.values - ? -2 - : a.values < b.values ? 2 : a.key > b.key ? -1 : 1; - }) - .map(function(d) { - return d.key; - }); - - //Capture all participant IDs with adverse events without a start day. - var withoutStartDay = d3 - .set( - filtered_data - .filter(function(d) { - return ( - +d[_this.config.seq_col] > 0 && - (isNaN(parseFloat(d[_this.config.stdy_col])) || - !isFinite(d[_this.config.stdy_col])) && - withStartDay.indexOf(d[_this.config.id_col]) === -1 - ); - }) - .map(function(d) { - return d[_this.config.id_col]; - }) - ) - .values(); - this.y_dom = withStartDay.concat(withoutStartDay); - } else this.y_dom = this.y_dom.sort(d3.descending); - } - - function onDraw() { - addNAToColorScale.call(this); - updateParticipantCount(this, '.annote', 'participant ID(s)'); - sortYdomain.call(this); - } - - /*------------------------------------------------------------------------------------------------\ - Add highlighted adverse event legend item. - \------------------------------------------------------------------------------------------------*/ - - function addHighlightLegendItem(chart) { - chart.wrap.select('.legend li.highlight').remove(); - var highlightLegendItem = chart.wrap - .select('.legend') - .append('li') - .attr('class', 'highlight') - .style({ - 'list-style-type': 'none', - 'margin-right': '1em', - display: 'inline-block' - }); - var highlightLegendColorBlock = highlightLegendItem - .append('svg') - .attr({ - width: '1.75em', - height: '1.5em' - }) - .style({ - position: 'relative', - top: '0.35em' - }); - highlightLegendColorBlock - .append('circle') - .attr({ - cx: 10, - cy: 10, - r: 4 - }) - .style(chart.config.highlight.attributes); - highlightLegendColorBlock - .append('line') - .attr({ - x1: 2 * 3.14 * 4 - 10, - y1: 10, - x2: 2 * 3.14 * 4 - 5, - y2: 10 - }) - .style(chart.config.highlight.attributes) - .style('shape-rendering', 'crispEdges'); - highlightLegendItem - .append('text') - .style('margin-left', '.35em') - .text(chart.config.highlight.label); - } - - function drawTopXaxis() { - var x2Axis = d3.svg - .axis() - .scale(this.x) - .orient('top') - .tickFormat(this.xAxis.tickFormat()) - .innerTickSize(this.xAxis.innerTickSize()) - .outerTickSize(this.xAxis.outerTickSize()) - .ticks(this.xAxis.ticks()[0]); - var g_x2_axis = this.svg.select('g.x2.axis').attr('class', 'x2 axis linear'); - g_x2_axis.call(x2Axis); - g_x2_axis - .select('text.axis-title.top') - .attr( - 'transform', - 'translate(' + this.raw_width / 2 + ',-' + this.config.margin.top + ')' - ); - g_x2_axis.select('.domain').attr({ - fill: 'none', - stroke: '#ccc', - 'shape-rendering': 'crispEdges' - }); - g_x2_axis.selectAll('.tick line').attr('stroke', '#eee'); - } - - function addTickClick() { - var _this = this; - - var context = this; - this.svg - .select('.y.axis') - .selectAll('.tick') - .style('cursor', 'pointer') - .on('click', function(d) { - var csv2 = _this.raw_data.filter(function(di) { - return di[_this.config.id_col] === d; - }); - _this.chart2.wrap.style('display', 'block'); - _this.chart2.draw(csv2); - _this.chart2.wrap - .select('#backButton') - .append('strong') - .attr('class', 'id-title') - .style('margin-left', '1%') - .text('Participant: ' + d); - - //Trigger participantsSelected event - context.participantsSelected = [d]; - context.events.participantsSelected.data = context.participantsSelected; - context.wrap.node().dispatchEvent(context.events.participantsSelected); - - //Sort listing by sequence. - var seq_col = context.config.seq_col; - var tableData = _this.superRaw - .filter(function(di) { - return di[_this.config.id_col] === d; - }) - .sort(function(a, b) { - return +a[seq_col] < b[seq_col] ? -1 : 1; - }); - - //Define listing columns. - _this.table.config.cols = d3 - .set( - _this.config.details.map(function(detail) { - return detail.value_col; - }) - ) - .values(); - _this.table.config.headers = d3 - .set( - _this.config.details.map(function(detail) { - return detail.label; - }) - ) - .values(); - _this.table.wrap.style('display', 'block'); - _this.table.draw(tableData); - _this.table.wrap.selectAll('th,td').style({ - 'text-align': 'left', - 'padding-right': '10px' - }); - - //Hide timelines. - _this.wrap.style('display', 'none'); - _this.controls.wrap.style('display', 'none'); - }); - } - - function onResize() { - var context = this; - - //Add highlight adverse event legend item. - if (this.config.highlight) addHighlightLegendItem(this); - - //Draw second x-axis at top of chart. - drawTopXaxis.call(this); - - //Draw second chart when y-axis tick label is clicked. - addTickClick.call(this); - - /**-------------------------------------------------------------------------------------------\ - Second chart callbacks. - \-------------------------------------------------------------------------------------------**/ - - this.chart2.on('preprocess', function() { - //Define color scale. - this.config.color_dom = context.colorScale.domain(); - }); - - this.chart2.on('draw', function() { - //Sync x-axis domain of second chart with that of the original chart. - this.x_dom = context.x_dom; - }); - - this.chart2.on('resize', function() { - //Add highlight adverse event legend item. - if (this.config.highlight) addHighlightLegendItem(this); - }); - } - - // utilities - - function aeTimelines() { - var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'body'; - var settings = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - - //Merge default settings with custom settings. - var mergedSettings = deepmerge_1(defaultSettings, settings, { - arrayMerge: function arrayMerge(destination, source) { - return source; - } - }); - - //Sync properties within settings object. - var syncedSettings = syncSettings(mergedSettings); - - //Sync control inputs with settings object. - var syncedControlInputs = syncControlInputs(controlInputs, syncedSettings); - - //Sync properties within secondary settings object. - var syncedSecondSettings = syncSecondSettings(syncedSettings); - - //Create controls. - var controls = webcharts.createControls(element, { - location: 'top', - inputs: syncedControlInputs - }); - - //Create chart. - var chart = webcharts.createChart(element, syncedSettings, controls); - chart.on('init', onInit); - chart.on('layout', onLayout); - chart.on('preprocess', onPreprocess); - chart.on('datatransform', onDatatransform); - chart.on('draw', onDraw); - chart.on('resize', onResize); - - //Create participant-level chart. - var chart2 = webcharts.createChart(element, syncedSecondSettings).init([]); - chart2.wrap.style('display', 'none'); - chart.chart2 = chart2; - - //Create participant-level listing. - var table = webcharts.createTable(element, {}).init([]); - table.wrap.style('display', 'none'); - table.table.style('display', 'table'); - table.table.attr('width', '100%'); - chart.table = table; - - return chart; - } - - return aeTimelines; -}); diff --git a/inst/htmlwidgets/lib/aeexplorer-3.3.5/aeTable.css b/inst/htmlwidgets/lib/aeexplorer-3.3.5/aeTable.css deleted file mode 100644 index 56916b7a..00000000 --- a/inst/htmlwidgets/lib/aeexplorer-3.3.5/aeTable.css +++ /dev/null @@ -1,369 +0,0 @@ -/*------------------------------------------------------------------------------------------------\ - AE Table style sheet -\------------------------------------------------------------------------------------------------*/ - - /***--------------------------------------------------------------------------------------\ - Table layout - \--------------------------------------------------------------------------------------***/ - - div.aeTable div.wc-navbar { - margin-top:5px; - } - - div.aeTable span.headingtext { - color:#377EB8; - } - - div.aeTable span.labeltext { - color:#BBB; - font-size:14px; - } - - div.aeTable div.wc-navbar .wc-dropdown-menu li { - cursor: pointer; - } - - div.aeTable div.wc-navbar ul.wc-dropdown-menu li a.disabled { - color: #BBB; - } - - div.aeExplorer .wc-alert{ - padding: 20px; - background-color: #f44336; /* Red */ - color: white; - margin-bottom: 15px; - border-radius:5px; - } - /***--------------------------------------------------------------------------------------\ - Controls layout - \--------------------------------------------------------------------------------------***/ - div.aeTable .controls .filterDiv{ - display:inline-block; - vertical-align:top; - padding-right:10px; - - } - - div.aeTable .controls .filterDiv.custom-filters{ - display:block; - } - - div.aeTable .controls .filterDiv.custom-filters select{ - min-width: 3em; - } - - - /*Rate filter*/ - div.aeTable .controls .rate-filter { - margin-bottom: 10px; - } - - div.aeTable .controls .rate-filter > * { - display: table-cell; - vertical-align: middle; - float: left; - } - - /*Summary Radio Buttons*/ - div.aeTable .controls .summary-control { - float: left; - margin-bottom: 10px; - } - - div.aeTable .controls .summary-control > * { - display: table-cell; - float: left; - } - - div.aeTable .controls .summary-control div { - display: table-cell; - vertical-align: middle; - float: left; - } - - div.aeTable .controls .summary-control div label:hover { - color: blue; - } - - div.aeTable .controls .summary-control div label input { - float: left; - } - - /*Rate Filter*/ - div.aeTable span.sectionHead { - font-weight: bold; - padding-right: 10px; - } - - div.aeTable .controls .rate-filter .rateFilterDiv > * { - font-size: small; - float: left; - padding: 3px; - display: table-cell; - line-height: 1.5em; - } - - div.aeTable .controls .rate-filter span.add-on { - background: #eee; - text-align: center; - text-shadow: 0 1px 0 #fff; - border: 1px solid #ccc; - } - - div.aeTable .controls .rate-filter span.add-on.before { - margin-right: -1px; - -webkit-border-radius: 4px 0 0 4px; - -moz-border-radius: 4px 0 0 4px; - border-radius: 4px 0 0 4px; - } - - div.aeTable .controls .rate-filter span.add-on.after { - margin-left: -1px; - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; - } - - div.aeTable .controls .rate-filter input { - text-align: right; - width: 40px; - border: 1px solid #ccc; - } - - /* Custom Filters */ - div.aeTable .controls .custom-filters { - clear: both; - } - - div.aeTable .controls .custom-filters ul li { - margin: 0 3px 5px 0; - } - - div.aeTable .controls .custom-filters ul li.wc-active > a > label, - div.aeTable .controls .custom-filters ul li:hover > a > label { - color: white; - } - - div.aeTable .controls .custom-filters ul li .filterType{ - font-size: 0.8em - } - - div.aeTable .controls .custom-filters ul li.filterCustom { - display:inline-block ; - padding-right:5px; - } - - div.aeTable .controls .custom-filters ul li.filterCustom * { - display:block ; - } - - div.aeTable .controls .custom-filters ul li.filterCustom span.filterLabel * { - display:inline-block ; - } - - div.aeTable .controls .custom-filters ul li.filterCustom span.filterLabel .filterType { - font-style:italic; - padding-left:3px; - cursor: pointer; - } - - /*Search*/ - div.aeTable span.search-label { - padding-left:5px; - margin-right:5px; - } - - div.aeTable span.search-label span.clear-search { - color:red; - padding-left:5px; - cursor:pointer; - } - - div.aeTable .wc-navbar-search .search-query { - line-height: 1.3em; - } - - /***--------------------------------------------------------------------------------------\ - Summary table formatting - \--------------------------------------------------------------------------------------***/ - - div.aeTable .SummaryTable table { - border-collapse:collapse; - } - - div.aeTable .SummaryTable tr td { - text-align:left; - padding: 0.5em 5px; - } - - td.rowLabel.highlight { - text-decoration:underline; - cursor:pointer; - } - - div.aeTable tr.major td.controls:hover { - cursor:pointer; - } - - /*Handles indentation for nested rows*/ - div.aeTable tr.minor td.rowLabel { - padding-left:15px; - } - - div.aeTable .SummaryTable tr td.values, - div.aeTable .SummaryTable tr td.prevplot, - div.aeTable .SummaryTable tr td.diffplot, - div.aeTable .SummaryTable th { - text-align:center; - } - - div.aeTable .SummaryTable th span.group-num { - font-size: 9pt; - } - - div.aeTable .SummaryTable thead tr:last-of-type th, - div.aeTable .SummaryTable thead tr th[rowspan="2"] { - border-bottom: 1px solid #555; - } - - div.aeTable .SummaryTable th, - div.aeTable .SummaryTable tfoot td { - padding: 0 5px; - background-color:#eee; - font-weight: bold; - border: none; - } - - /* alignment for column header/axes*/ - div.aeTable .SummaryTable th.prevHeader svg, - div.aeTable .SummaryTable th.diffplot.axis svg { - vertical-align: bottom; - } - - div.aeTable .SummaryTable th.prevHeader, - div.aeTable .SummaryTable th.diffplot.axis { - padding-bottom: 0px; - vertical-align:bottom; - } - - div.aeTable .SummaryTable th.prevHeader svg text, - div.aeTable .SummaryTable th.diffplot.axis svg text { - font-size: 10px; - } - - div.aeTable .SummaryTable tfoot, - div.aeTable .SummaryTable thead { - border-top:1px #555 solid; - border-bottom:1px #555 solid; - } - - div.aeTable .SummaryTable i { - cursor: pointer; - font-size: .8em; - top: 0px; - } - - /* Handle visibility in various scenarios */ - div.aeTable .SummaryTable tr.filter, - div.aeTable .SummaryTable table tbody.minorHidden tr.minor, - div.aeTable .SummaryTable tr.inactive, - div.aeTable .SummaryTable table.summary { - display:none; - } - - div.aeTable .wc-hidden, - div.aeTable .controls .wc-hidden, - div.aeTable .controls .filterDiv.custom-filters.wc-hidden { - display: none; - } - - /*using set widths to avoid funky behavior on mouseover - could maybe make the width interactive based on the data? */ - div.aeTable .SummaryTable tr td.values { - min-width:100px; - white-space: nowrap; - } - - div.aeTable .SummaryTable table tr:hover { - background-color:#eee; - } - - div.aeTable .SummaryTable button.closeDetailTable { - margin-right:auto; - margin-left:auto; - } - - /* Search Visibility */ - div.aeTable .SummaryTable.search tbody:not(.search) tr, .SummaryTable.search tbody.search tr:not(.search) { - display:none; - } - - div.aeTable .SummaryTable.search tbody.search tr.search, .SummaryTable.search tbody.search tr.major { - display:table-row; - } - - div.aeTable .SummaryTable.search tbody tr.major td.controls span.toggle.icon { - display:none; - } - - div.aeTable .SummaryTable.search tbody td.prevplot > svg > g.points > text { - font-size: 10px; - } - - div.aeTable span.search { - background:#feb24c; - color:black; - } - - div.aeTable span.icon.toggle { - color: inherit; - background-position: inherit; - } - - div.aeTable span.icon.toggle.transparent > i, - div.aeTable span.icon.details.transparent > i { - color: transparent; - background-position: 1000px; - } - - /***--------------------------------------------------------------------------------------\ - Summary plot formatting - \--------------------------------------------------------------------------------------***/ - - div.aeTable .axis path { - fill:none; - stroke:none; - } - - div.aeTable .axis line { - fill: none; - stroke: #999; - shape-rendering: crispEdges; - } - - div.aeTable .axis { - font-size:10px; - } - - /***--------------------------------------------------------------------------------------\ - Details table formatting - \--------------------------------------------------------------------------------------***/ - - div.aeTable .DetailTable h4 { - text-decoration: none; - font-weight: normal; - margin-top: 10px; - } - - div.aeTable .DetailTable h4 b { - text-decoration: none; - font-weight: bold; - } - - div.aeTable .DetailTable table { - font-size: 9pt; - } - - div.aeTable .DetailTable th { - padding: 3px 5px; - text-align: left; - } diff --git a/inst/htmlwidgets/lib/aeexplorer-3.3.5/aeTable.js b/inst/htmlwidgets/lib/aeexplorer-3.3.5/aeTable.js deleted file mode 100644 index a2dfc076..00000000 --- a/inst/htmlwidgets/lib/aeexplorer-3.3.5/aeTable.js +++ /dev/null @@ -1,2329 +0,0 @@ -(function(global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' - ? (module.exports = factory()) - : typeof define === 'function' && define.amd - ? define(factory) - : (global.aeTable = factory()); -})(this, function() { - 'use strict'; - /*------------------------------------------------------------------------------------------------\ - Initialize adverse event explorer. - \------------------------------------------------------------------------------------------------*/ - - function init(data) { - var settings = this.config; - - //create chart wrapper in specified div - this.wrap = d3.select(this.element).append('div'); - this.wrap.attr('class', 'aeExplorer'); - - //save raw data - this.raw_data = data; - - //settings and defaults - this.util.setDefaults(this); - this.layout(); - - //Flag placeholder rows in raw data save a separate event-only data set - var placeholderCol = this.config.defaults.placeholderFlag.value_col; - var placeholderValues = this.config.defaults.placeholderFlag.values; - this.raw_data.forEach(function(d) { - return (d.placeholderFlag = placeholderValues.indexOf(d[placeholderCol]) > -1); - }); - this.raw_event_data = data.filter(function(d) { - return !d.placeholderFlag; - }); - //draw controls and initial chart - this.controls.init(this); - this.AETable.redraw(this); - } - - /*------------------------------------------------------------------------------------------------\ - Set colors. - \------------------------------------------------------------------------------------------------*/ - - var colorScale = d3.scale - .ordinal() - .range(['#377EB8', '#4DAF4A', '#984EA3', '#FF7F00', '#A65628', '#F781BF', '#E41A1C']); - - /*------------------------------------------------------------------------------------------------\ - Generate HTML containers. - \------------------------------------------------------------------------------------------------*/ - - function layout() { - var wrapper = this.wrap - .append('div') - .attr('class', 'aeTable') - .append('div') - .attr('class', 'table-wrapper'); - wrapper.append('div').attr('class', 'controls'); - wrapper.append('div').attr('class', 'SummaryTable'); - if (this.config.validation) - this.wrap - .append('a') - .attr({ - id: 'downloadCSV' - }) - .text('Download Summarized Data'); - } - - /*------------------------------------------------------------------------------------------------\ - Initialize controls. - \------------------------------------------------------------------------------------------------*/ - - function init$1(chart) { - chart.controls.wrap = chart.wrap.select('div.controls'); - chart.controls.wrap.attr('onsubmit', 'return false;'); - chart.controls.wrap.selectAll('*').remove(); //Clear controls. - - //Draw variable controls if options are specified - if (chart.config.defaults.useVariableControls) { - var optionList = ['id', 'major', 'minor', 'group']; - optionList.forEach(function(option) { - if (chart.config.variableOptions[option].length > 1) { - chart.controls.variableSelect.init(chart, option); - } - }); - } - - //Draw standard UI components - chart.controls.filters.rate.init(chart); - chart.controls.summaryControl.init(chart); - chart.controls.search.init(chart); - chart.controls.filters.custom.init(chart); - - //Initialize the filter rate. - chart.controls.filters.rate.set(chart); - - //assign filterDiv class to all filter wrappers - chart.controls.wrap.selectAll('div').classed('filterDiv', true); - } - - /*------------------------------------------------------------------------------------------------\ - Initialize rate filter. - \------------------------------------------------------------------------------------------------*/ - - function init$2(chart) { - //create the wrapper - var selector = chart.controls.wrap.append('div').attr('class', 'rate-filter'); - - //Clear rate filter. - selector.selectAll('span.filterLabel, div.rateFilterDiv').remove(); - - //Generate rate filter. - selector.append('span').attr('class', 'sectionHead').text('Filter by prevalence:'); - - var rateFilter = selector - .append('div') - .attr('class', 'input-prepend input-append input-medium rateFilterDiv'); - rateFilter.append('span').attr('class', 'add-on before').html('≥'); - rateFilter.append('input').attr({ - class: 'appendedPrependedInput rateFilter', - type: 'number' - }); - rateFilter.append('span').attr('class', 'add-on after').text('%'); - - //event listener - rateFilter.on('input', function(d) { - //Clear filter flags. - chart.wrap.selectAll('.SummaryTable table tbody tr').classed('filter', false); - - //Add filter flags. - chart.AETable.toggleRows(chart); - }); - } - - /*------------------------------------------------------------------------------------------------\ - Set rate filter default. - \------------------------------------------------------------------------------------------------*/ - - function set(chart) { - chart.controls.wrap - .select('input.rateFilter') - .property( - 'value', - chart.config.defaults.maxPrevalence ? chart.config.defaults.maxPrevalence : 0 - ); - } - - /*------------------------------------------------------------------------------------------------\ - Define rate filter object. - \------------------------------------------------------------------------------------------------*/ - - var rate = { - init: init$2, - set: set - }; - - /*------------------------------------------------------------------------------------------------\ - Initialize custom controls. - \------------------------------------------------------------------------------------------------*/ - - //export function init(selector, data, vars, settings) { - function init$3(chart) { - //initialize the wrapper - var selector = chart.controls.wrap.append('div').attr('class', 'custom-filters'); - - //add a list of values to each filter object - chart.config.variables.filters.forEach(function(e) { - var currentData = e.type == 'participant' ? chart.raw_data : chart.raw_event_data; - e.values = d3 - .nest() - .key(function(d) { - return d[e.value_col]; - }) - .entries(currentData) - .map(function(d) { - return d.key; - }); - }); - - //drop filters with 0 or 1 levels and throw a warning - chart.config.variables.filters = chart.config.variables.filters.filter(function(d) { - if (d.values.length <= 1) { - console.warn( - d.value_col + ' filter not shown since the variable has less than 2 levels' - ); - } - return d.values.length > 1; - }); - - //Clear custom controls. - selector.selectAll('ul.nav').remove(); - - //Add filter controls. - var filterList = selector.append('ul').attr('class', 'nav'); - var filterItem = filterList - .selectAll('li') - .data(chart.config.variables.filters) - .enter() - .append('li') - .attr('class', function(d) { - return 'custom-' + d.key + ' filterCustom'; - }); - var filterLabel = filterItem.append('span').attr('class', 'filterLabel'); - - filterLabel.append('span').html(function(d) { - return d.label || d.value_col; - }); - - filterLabel - .append('sup') - .attr('class', 'filterType') - .text(function(d) { - return d.type == 'event' ? 'E' : 'P'; - }) - .property('title', function(d) { - return d.type == 'event' - ? 'Event filter: Changes rate counts only. Does not change population.' - : 'Participant filter: Changes rate counts and populations.'; - }); - - var filterCustom = filterItem.append('select').attr('multiple', true); - - //Add data-driven filter options. - var filterItems = filterCustom - .selectAll('option') - .data(function(d) { - return d.values.map(function(di) { - return { - value: di, - selected: Array.isArray(d.start) && d.start.length - ? d.start.indexOf(di) > -1 - : true - }; - }); - }) - .enter() - .append('option') - .html(function(d) { - return d.value; - }) - .attr('value', function(d) { - return d.value; - }) - .attr('selected', function(d) { - return d.selected ? 'selected' : null; - }); - - //Initialize event listeners - filterCustom.on('change', function() { - chart.AETable.redraw(chart); - }); - } - - /*------------------------------------------------------------------------------------------------\ - Define custom filters object. - \------------------------------------------------------------------------------------------------*/ - - var custom = { init: init$3 }; - - /*------------------------------------------------------------------------------------------------\ - Define filter controls object. - \------------------------------------------------------------------------------------------------*/ - - var filters = { - rate: rate, - custom: custom - }; - - /*------------------------------------------------------------------------------------------------\ - - Initialize summary control. - - \------------------------------------------------------------------------------------------------*/ - - function init$4(chart) { - //set the initial summary status - chart.config.summary = chart.config.defaults.summarizeBy; - - //create element - var selector = chart.controls.wrap.append('div').attr('class', 'summary-control'); - - //Clear summary control. - selector.selectAll('div.summaryDiv').remove(); - - //Generate summary control. - selector.append('span').attr('class', 'sectionHead').text('Summarize by:'); - - var summaryControl = selector - .append('div') - .attr('class', 'input-prepend input-append input-medium summaryDiv'); - summaryControl - .selectAll('div') - .data(['participant', 'event']) - .enter() - .append('div') - .append('label') - .style('font-weight', function(d) { - return d === chart.config.summary ? 'bold' : null; - }) - .text(function(d) { - return d; - }) - .append('input') - .attr({ - class: 'appendedPrependedInput summaryRadio', - type: 'radio' - }) - .property('checked', function(d) { - return d === chart.config.summary; - }); - - //initialize event listener - var radios = chart.wrap.selectAll('div.summaryDiv .summaryRadio'); - radios.on('change', function(d) { - radios.each(function(di) { - d3.select(this.parentNode).style('font-weight', 'normal'); - d3.select(this)[0][0].checked = false; - }); - d3.select(this)[0][0].checked = true; - d3.select(this.parentNode).style('font-weight', 'bold'); - chart.config.summary = d3.select(this.parentNode)[0][0].textContent; - chart.AETable.redraw(chart); - }); - } - - /*------------------------------------------------------------------------------------------------\ - Define search control object. - \------------------------------------------------------------------------------------------------*/ - - var summaryControl = { init: init$4 }; - - function init$5(chart, variable) { - var selector = chart.controls.wrap.append('div').attr('class', 'variable-control variable'); - - //Clear summary control. - selector.selectAll('div.summaryDiv').remove(); - - //Generate summary control. - var labels = { - major: 'Major Category Variable:', - minor: 'Minor Category Variable:', - group: 'Group Variable:', - id: 'ID Variable:' - }; - selector.append('span').attr('class', 'sectionHead').text(labels[variable]); - - var variableControl = selector.append('select'); - - variableControl - .selectAll('option') - .data(chart.config.variableOptions[variable]) - .enter() - .append('option') - .text(function(d) { - return d; - }) - .property('selected', function(d) { - if ((variable == 'group') & !chart.config.defaults.groupCols) { - return d == 'None'; - } else { - return d === chart.config.variables[variable]; - } - }); - - //initialize event listener - variableControl.on('change', function(d) { - var current = this.value; - if (current != 'None') chart.config.variables[variable] = current; - - //update config.groups if needed - console.log(chart); - if (variable == 'group') { - if (current == 'None') { - chart.config.defaults.diffCol = false; - chart.config.defaults.groupCols = false; - chart.config.defaults.totalCol = true; - } else { - chart.config.defaults.groupCols = true; - chart.config.defaults.diffCol = true; - } - - //update the groups setting - var allGroups = d3 - .set( - chart.raw_data.map(function(d) { - return d[chart.config.variables.group]; - }) - ) - .values(); - var groupsObject = allGroups.map(function(d) { - return { key: d }; - }); - chart.config.groups = groupsObject.sort(function(a, b) { - return a.key < b.key ? -1 : a.key > b.key ? 1 : 0; - }); - - //update the color scale - var levels = chart.config.groups.map(function(e) { - return e.key; - }); - var colors = [ - '#377EB8', - '#4DAF4A', - '#984EA3', - '#FF7F00', - '#A65628', - '#F781BF', - '#E41A1C' - ]; - if (chart.config.defaults.totalCol) - //Set 'Total' column color to #777. - colors[chart.config.groups.length] = '#777'; - - chart.colorScale.range(colors).domain(levels); - } - - //Check to see if there are too many levels in the new group variable - if ( - (chart.config.groups.length > chart.config.defaults.maxGroups) & - (current != 'None') - ) { - chart.wrap - .select('.aeTable') - .select('.table-wrapper') - .select('.SummaryTable') - .style('display', 'none'); - var errorText = - 'Too Many Group Variables specified. You specified ' + - chart.config.groups.length + - ', but the maximum supported is ' + - chart.config.defaults.maxGroups + - '.'; - chart.wrap.selectAll('div.wc-alert').remove(); - chart.wrap - .append('div') - .attr('class', 'wc-alert') - .text('Fatal Error: ' + errorText); - throw new Error(errorText); - } else { - chart.wrap - .select('.aeTable') - .select('.table-wrapper') - .select('.SummaryTable') - .style('display', null); - chart.wrap.selectAll('div.wc-alert').remove(); - chart.AETable.redraw(chart); - } - }); - } - - /*------------------------------------------------------------------------------------------------\ - Define search control object. - \------------------------------------------------------------------------------------------------*/ - - var variableSelect = { init: init$5 }; - - /*------------------------------------------------------------------------------------------------\ - Initialize search control. - \------------------------------------------------------------------------------------------------*/ - - function init$6(chart) { - //draw the search control - var selector = chart.controls.wrap - .append('div') - .attr('class', 'searchForm wc-navbar-search pull-right') - .attr('onsubmit', 'return false;'); - - //Clear search control. - selector.selectAll('span.seach-label, input.searchBar').remove(); - - //Generate search control. - var searchLabel = selector.append('span').attr('class', 'search-label label wc-hidden'); - searchLabel.append('span').attr('class', 'search-count'); - searchLabel.append('span').attr('class', 'clear-search').html('☓'); - selector - .append('input') - .attr('type', 'text') - .attr('class', 'searchBar search-query input-medium') - .attr('placeholder', 'Search'); - - //event listeners for search - chart.wrap.select('input.searchBar').on('input', function(d) { - var searchTerm = d3.select(this).property('value').toLowerCase(); - - if (searchTerm.length > 0) { - //Clear the previous search but preserve search text. - chart.controls.search.clear(chart); - d3.select(this).property('value', searchTerm); - - //Clear flags. - chart.wrap.selectAll('div.SummaryTable table tbody').classed('minorHidden', false); - chart.wrap.selectAll('div.SummaryTable table tbody tr').classed('filter', false); - chart.wrap.select('div.SummaryTable').classed('search', false); - chart.wrap.selectAll('div.SummaryTable table tbody').classed('search', false); - chart.wrap.selectAll('div.SummaryTable table tbody tr').classed('search', false); - - //Hide expand/collapse cells. - chart.wrap - .selectAll('div.SummaryTable table tbody tr td.controls span') - .classed('wc-hidden', true); - - //Display 'clear search' icon. - chart.wrap.select('span.search-label').classed('wc-hidden', false); - - //Flag summary table. - var tab = chart.wrap.select('div.SummaryTable').classed('search', true); - - //Capture rows which contain the search term. - var tbodyMatch = tab.select('table').selectAll('tbody').each(function(bodyElement) { - var bodyCurrent = d3.select(this); - var bodyData = bodyCurrent.data()[0]; - - bodyCurrent.selectAll('tr').each(function(rowElement) { - var rowCurrent = d3.select(this); - var rowData = rowCurrent.data()[0]; - var rowText = rowCurrent.classed('major') - ? bodyData.key.toLowerCase() - : rowData.key.toLowerCase(); - - if (rowText.search(searchTerm) >= 0) { - bodyCurrent.classed('search', true); - rowCurrent.classed('search', true); - - //Highlight search text in selected table cell. - var currentText = rowCurrent.select('td.rowLabel').html(); - var searchStart = currentText.toLowerCase().search(searchTerm); - var searchStop = searchStart + searchTerm.length; - var newText = - currentText.slice(0, searchStart) + - '' + - currentText.slice(searchStart, searchStop) + - '' + - currentText.slice(searchStop, currentText.length); - rowCurrent.select('td.rowLabel').html(newText); - } - }); - }); - - //Disable the rate filter. - d3.select('input.rateFilter').property('disabled', true); - - //Update the search label. - var matchCount = chart.wrap.selectAll('tr.search')[0].length; - chart.wrap.select('span.search-count').text(matchCount + ' matches'); - chart.wrap - .select('span.search-label') - .attr( - 'class', - matchCount === 0 - ? 'search-label label label-warning' - : 'search-label label label-success' - ); - - //Check whether search term returned zero matches. - if (matchCount === 0) { - //Restore the table. - chart.wrap.selectAll('div.SummaryTable').classed('search', false); - chart.wrap.selectAll('div.SummaryTable table tbody').classed('search', false); - chart.wrap - .selectAll('div.SummaryTable table tbody tr') - .classed('search', false); - - //Reset the filters and row toggle. - chart.AETable.toggleRows(chart); - } - } else chart.controls.search.clear(chart); - }); - - chart.wrap.select('span.clear-search').on('click', function() { - chart.controls.search.clear(chart); - }); - } - - /*------------------------------------------------------------------------------------------------\ - Clear search term results. - \------------------------------------------------------------------------------------------------*/ - - function clear(chart) { - //Re-enable rate filter. - chart.wrap.select('input.rateFilter').property('disabled', false); - - //Clear search box. - chart.wrap.select('input.searchBar').property('value', ''); - - //Remove search highlighting. - chart.wrap - .selectAll('div.SummaryTable table tbody tr.search td.rowLabel') - .html(function(d) { - return d.values[0].values['label']; - }); - - //Remove 'clear search' icon and label. - chart.wrap.select('span.search-label').classed('wc-hidden', true); - - //Clear search flags. - chart.wrap.selectAll('div.SummaryTable').classed('search', false); - chart.wrap.selectAll('div.SummaryTable table tbody').classed('search', false); - chart.wrap.selectAll('div.SummaryTable table tbody tr').classed('search', false); - - //Reset filters and row toggle. - chart.AETable.toggleRows(chart); - } - - /*------------------------------------------------------------------------------------------------\ - Define search control object. - \------------------------------------------------------------------------------------------------*/ - - var search = { - init: init$6, - clear: clear - }; - - /*------------------------------------------------------------------------------------------------\ - Define controls object. - \------------------------------------------------------------------------------------------------*/ - - var controls = { - init: init$1, - filters: filters, - summaryControl: summaryControl, - variableSelect: variableSelect, - search: search - }; - - /*------------------------------------------------------------------------------------------------\ - Clear the current chart and draw a new one. - \------------------------------------------------------------------------------------------------*/ - - function redraw(chart) { - chart.controls.search.clear(chart); - chart.AETable.wipe(chart.wrap); - chart.util.prepareData(chart); - chart.AETable.init(chart); - chart.AETable.toggleRows(chart); - } - - /*------------------------------------------------------------------------------------------------\ - Clears the summary or detail table and all associated buttons. - \------------------------------------------------------------------------------------------------*/ - - function wipe(canvas) { - canvas.select('.table-wrapper .SummaryTable .wc-alert').remove(); - canvas.select('.table-wrapper .SummaryTable table').remove(); - canvas.select('.table-wrapper .SummaryTable button').remove(); - canvas.select('.table-wrapper .DetailTable').remove(); - canvas.select('.table-wrapper .DetailTable').remove(); - } - - /*------------------------------------------------------------------------------------------------\ - - annoteDetails(table, canvas, row, group) - - Convenience function that shows the raw #s and annotates point values for a single group - - + table - - AE table object - + rows - - highlighted row(s) (selection containing 'tr' objects) - + group - - group to highlight - - \------------------------------------------------------------------------------------------------*/ - - function showCellCounts(chart, rows, group) { - //Add raw counts for the specified row/groups . - rows - .selectAll('td.values') - .filter(function(e) { - return e.key === group; - }) - .append('span.annote') - .classed('annote', true) - .text(function(d) { - return ' (' + d['values'].n + '/' + d['values'].tot + ')'; - }); - } - - /*------------------------------------------------------------------------------------------------\ - Calculate differences between groups. - \------------------------------------------------------------------------------------------------*/ - - function calculateDifference(major, minor, group1, group2, n1, tot1, n2, tot2) { - var p1 = n1 / tot1; - var p2 = n2 / tot2; - var diff = p1 - p2; - var se = Math.sqrt(p1 * (1 - p1) / tot1 + p2 * (1 - p2) / tot2); - var lower = diff - 1.96 * se; - var upper = diff + 1.96 * se; - var sig = (lower > 0) | (upper < 0) ? 1 : 0; - var summary = { - major: major, - minor: minor, - - group1: group1, - n1: n1, - tot1: tot1, - p1: p1, - - group2: group2, - n2: n2, - tot2: tot2, - p2: p2, - - diff: diff * 100, - lower: lower * 100, - upper: upper * 100, - sig: sig - }; - - return summary; - } - - /*------------------------------------------------------------------------------------------------\ - Add differences to data object. - \------------------------------------------------------------------------------------------------*/ - - function addDifferences(data, groups) { - var nGroups = groups.length; - - if (nGroups > 1) { - data.forEach(function(major) { - major.values.forEach(function(minor) { - minor.differences = []; - - var groups = minor.values; - var otherGroups = [].concat(minor.values); - - groups.forEach(function(group) { - delete otherGroups[ - otherGroups - .map(function(m) { - return m.key; - }) - .indexOf(group.key) - ]; - otherGroups.forEach(function(otherGroup) { - var diff = calculateDifference( - major.key, - minor.key, - group.key, - otherGroup.key, - group.values.n, - group.values.tot, - otherGroup.values.n, - otherGroup.values.tot - ); - minor.differences.push(diff); - }); - }); - }); - }); - } - - return data; - } - - /*------------------------------------------------------------------------------------------------\ - Calculate number of events, number of subjects, and adverse event rate by major, minor, and - group. - \------------------------------------------------------------------------------------------------*/ - - function cross(data, groups, id, major, minor, group) { - var groupNames = groups.map(function(d) { - return d.key; - }); - var summary = d3.selectAll('.summaryDiv label').filter(function(d) { - return d3.select(this).selectAll('.summaryRadio').property('checked'); - })[0][0].textContent; - - //Calculate [id] and event frequencies and rates by [major], [minor], and [group]. - var nestedData = d3 - .nest() - .key(function(d) { - return major == 'All' ? 'All' : d[major]; - }) - .key(function(d) { - return minor == 'All' ? 'All' : d[minor]; - }) - .key(function(d) { - return d[group]; - }) - .rollup(function(d) { - var selection = {}; - - //Category - selection.major = major === 'All' ? 'All' : d[0][major]; - selection.minor = minor === 'All' ? 'All' : d[0][minor]; - selection.label = selection.minor === 'All' ? selection.major : selection.minor; - selection.group = d[0][group]; - - var currentGroup = groups.filter(function(di) { - return di.key === d[0][group]; - }); - - //Numerator/denominator - if (summary === 'participant') { - var ids = d3 - .nest() - .key(function(di) { - return di[id]; - }) - .entries(d); - selection.n = ids.length; - selection.tot = currentGroup[0].n; - } else { - selection.n = d.length; - selection.tot = currentGroup[0].nEvents; - } - - //Rate - selection.per = Math.round(selection.n / selection.tot * 1000) / 10; - - return selection; - }) - .entries(data); - - //Generate data objects for major*minor*group combinations absent in data. - nestedData.forEach(function(dMajor) { - dMajor.values.forEach(function(dMinor) { - var currentGroupList = dMinor.values.map(function(d) { - return d.key; - }); - - groupNames.forEach(function(dGroup, groupIndex) { - if (currentGroupList.indexOf(dGroup) === -1) { - var currentGroup = groups.filter(function(d) { - return d.key === dGroup; - }); - var tot = summary === 'participant' - ? currentGroup[0].n - : currentGroup[0].nEvents; - - var shellMajorMinorGroup = { - key: dGroup, - values: { - major: dMajor.key, - minor: dMinor.key, - label: dMinor.key === 'All' ? dMajor.key : dMinor.key, - group: dGroup, - - n: 0, - tot: tot, - per: 0 - } - }; - - dMinor.values.push(shellMajorMinorGroup); - } - }); - - dMinor.values.sort(function(a, b) { - return ( - groups - .map(function(group) { - return group.key; - }) - .indexOf(a.key) - - groups - .map(function(group) { - return group.key; - }) - .indexOf(b.key) - ); - }); - }); - }); - - return nestedData; - } - - /*------------------------------------------------------------------------------------------------\ - Define sorting algorithms. - \------------------------------------------------------------------------------------------------*/ - - var sort = { - //Sort by descending frequency. - maxPer: function maxPer(a, b) { - var max_a = a.values.map(function(minor) { - var n = d3.sum( - minor.values.map(function(group) { - return group.values.n; - }) - ); - var tot = d3.sum( - minor.values.map(function(group) { - return group.values.tot; - }) - ); - return n / tot; - })[0]; - var max_b = b.values.map(function(minor) { - var n = d3.sum( - minor.values.map(function(group) { - return group.values.n; - }) - ); - var tot = d3.sum( - minor.values.map(function(group) { - return group.values.tot; - }) - ); - return n / tot; - })[0]; - var diff = max_b - max_a; - - return diff ? diff : a.key < b.key ? -1 : 1; - } - }; - - /**-------------------------------------------------------------------------------------------\ - - fillrow(currentRow, chart, d) - - inputs (all required): - currentRow = d3.selector for a 'tr' element - chart = the chart object - d = the raw data for the row - - - Convienence function which fills each table row and draws the plots. - - + Note1: We'll call this 3x. Once for the major rows, once for - the minor rows and once for overall row. - - + Note2: Would be good to split out separate plotting functions if - this gets too much more complex. - - \-------------------------------------------------------------------------------------------**/ - - function fillRow(currentRow, chart, d) { - var table = chart; - //Append major row expand/collapse control. - var controlCell = currentRow.append('td').attr('class', 'controls'); - - if (d.key === 'All') { - controlCell.append('span').attr('title', 'Expand').text('+'); - } - - //Append row label. - var category = currentRow.append('td').attr({ - class: 'rowLabel', - title: 'Show listing' - }); - category.append('a').text(function(rowValues) { - return rowValues.values[0].values['label']; - }); - - //Calculate total frequency, number of records, population denominator, and rate. - if (chart.config.defaults.totalCol) { - var total = {}; - total.major = d.values[0].values.major; - total.minor = d.values[0].values.minor; - total.label = d.values[0].values.label; - total.group = 'Total'; - - total.n = d3.sum(d.values, function(di) { - return di.values.n; - }); - total.tot = d3.sum(d.values, function(di) { - return di.values.tot; - }); - - total.per = total.n / total.tot * 100; - - d.values[d.values.length] = { - key: 'Total', - values: total - }; - } - - //Append textual rates. - var values = currentRow - .selectAll('td.values') - .data(d.values, function(d) { - return d.key; - }) - .enter() - .append('td') - .attr('class', 'values') - .classed('total', function(d) { - return d.key == 'Total'; - }) - .classed('wc-hidden', function(d) { - if (d.key == 'Total') { - return !chart.config.defaults.totalCol; - } else { - return !chart.config.defaults.groupCols; - } - }) - .attr('title', function(d) { - return d.values.n + '/' + d.values.tot; - }) - .text(function(d) { - return d3.format('0.1f')(d['values'].per) + '%'; - }) - .style('color', function(d) { - return table.colorScale(d.key); - }); - - //Append graphical rates. - var prevalencePlot = currentRow - .append('td') - .classed('prevplot', true) - .append('svg') - .attr('height', chart.config.plotSettings.h) - .attr('width', chart.config.plotSettings.w + 10) - .append('svg:g') - .attr('transform', 'translate(5,0)'); - - var points = prevalencePlot - .selectAll('g.points') - .data(d.values) - .enter() - .append('g') - .attr('class', 'points'); - - points - .append('svg:circle') - .attr('cx', function(d) { - return chart.percentScale(d.values['per']); - }) - .attr('cy', chart.config.plotSettings.h / 2) - .attr('r', chart.config.plotSettings.r - 2) - .attr('fill', function(d) { - return table.colorScale(d.values['group']); - }) - .classed('wc-hidden', function(d) { - if (d.key == 'Total') { - return !chart.config.defaults.totalCol; - } else { - return !chart.config.defaults.groupCols; - } - }) - .append('title') - .text(function(d) { - return d.key + ': ' + d3.format(',.1%')(d.values.per / 100); - }); - - //Handle rate differences between groups if settings reference more then one group. - if (chart.config.groups.length > 1 && chart.config.defaults.diffCol) { - //Append container for group rate differences. - var differencePlot = currentRow - .append('td') - .classed('diffplot', true) - .append('svg') - .attr('height', chart.config.plotSettings.h) - .attr('width', chart.config.plotSettings.w + 10) - .append('svg:g') - .attr('transform', 'translate(5,0)'); - - var diffPoints = differencePlot - .selectAll('g') - .data(d.differences) - .enter() - .append('svg:g'); - diffPoints.append('title').text(function(d) { - return ( - d.group1 + - ' (' + - d3.format(',.1%')(d.p1) + - ') vs. ' + - d.group2 + - ' (' + - d3.format(',.1%')(d.p2) + - '): ' + - d3.format(',.1%')(d.diff / 100) - ); - }); - - //Append graphical rate difference confidence intervals. - diffPoints - .append('svg:line') - .attr('x1', function(d) { - return chart.diffScale(d.upper); - }) - .attr('x2', function(d) { - return chart.diffScale(d.lower); - }) - .attr('y1', chart.config.plotSettings.h / 2) - .attr('y2', chart.config.plotSettings.h / 2) - .attr('class', 'ci') - .classed('wc-hidden', chart.config.groups.length > 2) - .attr('stroke', '#bbb'); - - //Append graphical rate differences. - var triangle = d3.svg - .line() - .x(function(d) { - return d.x; - }) - .y(function(d) { - return d.y; - }) - .interpolate('linear-closed'); - - diffPoints - .append('svg:path') - .attr('d', function(d) { - var h = chart.config.plotSettings.h, - r = chart.config.plotSettings.r; - - var leftpoints = [ - { x: chart.diffScale(d.diff), y: h / 2 + r }, //bottom - { x: chart.diffScale(d.diff) - r, y: h / 2 }, //middle-left - { - x: chart.diffScale(d.diff), - y: h / 2 - r //top - } - ]; - return triangle(leftpoints); - }) - .attr('class', 'diamond') - .attr('fill-opacity', function(d) { - return d.sig === 1 ? 1 : 0.1; - }) - .attr('fill', function(d) { - return d.diff < 0 ? chart.colorScale(d.group1) : chart.colorScale(d.group2); - }) - .attr('stroke', function(d) { - return d.diff < 0 ? chart.colorScale(d.group1) : chart.colorScale(d.group2); - }) - .attr('stroke-opacity', 0.3); - - diffPoints - .append('svg:path') - .attr('d', function(d) { - var h = chart.config.plotSettings.h, - r = chart.config.plotSettings.r; - - var rightpoints = [ - { x: chart.diffScale(d.diff), y: h / 2 + r }, //bottom - { x: chart.diffScale(d.diff) + r, y: h / 2 }, //middle-right - { - x: chart.diffScale(d.diff), - y: h / 2 - r //top - } - ]; - return triangle(rightpoints); - }) - .attr('class', 'diamond') - .attr('fill-opacity', function(d) { - return d.sig === 1 ? 1 : 0.1; - }) - .attr('fill', function(d) { - return d.diff < 0 ? chart.colorScale(d.group2) : chart.colorScale(d.group1); - }) - .attr('stroke', function(d) { - return d.diff < 0 ? chart.colorScale(d.group2) : chart.colorScale(d.group1); - }) - .attr('stroke-opacity', 0.3); - } - } - - /*------------------------------------------------------------------------------------------------\ - Collapse data for export to .csv. - \------------------------------------------------------------------------------------------------*/ - - function collapse(nested) { - //Collapse nested object. - var collapsed = nested.map(function(soc) { - var allRows = soc.values.map(function(e) { - var eCollapsed = {}; - eCollapsed.majorCategory = e.values[0].values.major; - eCollapsed.minorCategory = e.values[0].values.minor; - - e.values.forEach(function(val, i) { - var n = i + 1; - eCollapsed['val' + n + '_label'] = val.key; - eCollapsed['val' + n + '_numerator'] = val.values.n; - eCollapsed['val' + n + '_denominator'] = val.values.tot; - eCollapsed['val' + n + '_percent'] = val.values.per; - }); - - if (e.differences) { - e.differences.forEach(function(diff, i) { - var n = i + 1; - eCollapsed['diff' + n + '_label'] = diff.group1 + '-' + diff.group2; - eCollapsed['diff' + n + '_val'] = diff['diff']; - eCollapsed['diff' + n + '_sig'] = diff['sig']; - }); - } - return eCollapsed; - }); - return allRows; - }); - return d3.merge(collapsed); - } - - function json2csv(chart) { - var majorValidation = collapse(chart.data.major), - // flatten major data array - minorValidation = collapse(chart.data.minor), - // flatten minor data array - fullValidation = d3 - .merge([majorValidation, minorValidation]) // combine flattened major and minor data arrays - .sort(function(a, b) { - return a.majorCategory < b.majorCategory - ? -1 - : a.majorCategory > b.majorCategory - ? 1 - : a.minorCategory < b.minorCategory ? -1 : 1; - }), - CSVarray = []; - - fullValidation.forEach(function(d, i) { - //add headers to CSV array - if (i === 0) { - var headers = Object.keys(d).map(function(key) { - return '"' + key.replace(/"/g, '""') + '"'; - }); - CSVarray.push(headers); - } - - //add rows to CSV array - var row = Object.keys(d).map(function(key) { - if (typeof d[key] === 'string') d[key] = d[key].replace(/"/g, '""'); - - return '"' + d[key] + '"'; - }); - - CSVarray.push(row); - }); - - //transform CSV array into CSV string - var CSV = new Blob([CSVarray.join('\n')], { type: 'text/csv;charset=utf-8;' }), - fileName = - chart.config.variables.major + - '-' + - chart.config.variables.minor + - '-' + - chart.config.summary + - '.csv', - link = chart.wrap.select('#downloadCSV'); - - if (navigator.msSaveBlob) { - // IE 10+ - link.style({ - cursor: 'pointer', - 'text-decoration': 'underline', - color: 'blue' - }); - link.on('click', function() { - navigator.msSaveBlob(CSV, fileName); - }); - } else { - // Browsers that support HTML5 download attribute - if (link.node().download !== undefined) { - // feature detection - var url = URL.createObjectURL(CSV); - link.node().setAttribute('href', url); - link.node().setAttribute('download', fileName); - } - } - - return CSVarray; - } - - /*------------------------------------------------------------------------------------------------\ - Filter the raw data per the current filter and group selections. - After this function is executed there should be 4 data objects bound to the chart: - (1) raw_data: an exact copy of the raw data, with an added "placeholderFlag" variable for participants with no events - (2) raw_event_data: an exact copy of the raw data with placeholder rows removed - (3) population_data: a copy of the raw data filtered by: - (a) chart.config.groups - rows from groups not included in the settings object are removed - (b) charts.config.variables.filters[type=="participant"] - according to current user selections - (4) population_event_data a copy of the population_data with: - (a) placeholder rows removed - (b) filtered by charts.config.variables.filters[type=="participant"] - according to current user selections - \------------------------------------------------------------------------------------------------*/ - function prepareData(chart) { - var vars = chart.config.variables; //convenience mapping - - //get filter information - chart.config.variables.filters.forEach(function(filter_d) { - //get a list of values that are currently selected - filter_d.currentValues = []; - var thisFilter = chart.wrap - .select('.custom-filters') - .selectAll('select') - .filter(function(select_d) { - return select_d.value_col == filter_d.value_col; - }); - thisFilter.selectAll('option').each(function(option_d) { - if (d3.select(this).property('selected')) { - filter_d.currentValues.push(option_d.value); - } - }); - }); - ///////////////////////////////// - //Create chart.population_data - ///////////////////////////////// - - //Subset data on groups specified in chart.config.groups. - var groupNames = chart.config.groups.map(function(d) { - return d.key; - }); - chart.population_data = chart.raw_data.filter(function(d) { - return groupNames.indexOf(d[vars['group']]) >= 0; - }); - - //Filter data to reflect the current population (based on filters where type = `participant`) - chart.config.variables.filters - .filter(function(d) { - return d.type == 'participant'; - }) - .forEach(function(filter_d) { - //remove the filtered values from the population data - chart.population_data = chart.population_data.filter(function(rowData) { - return filter_d.currentValues.indexOf('' + rowData[filter_d.value_col]) > -1; - }); - }); - - /////////////////////////////////////// - // create chart.population_event_data - //////////////////////////////////////// - - // Filter event level data - chart.population_event_data = chart.population_data.filter(function(d) { - return !d.placeholderFlag; - }); - - chart.config.variables.filters - .filter(function(d) { - return d.type == 'event'; - }) - .forEach(function(filter_d) { - //remove the filtered values from the population data - chart.population_event_data = chart.population_event_data.filter(function(rowData) { - return filter_d.currentValues.indexOf('' + rowData[filter_d.value_col]) > -1; - }); - }); - - //////////////////////////////////////////////////////////////////////// - // add group-level participant and event counts to chart.config.groups - // Used in group headers and to calculate rates - //////////////////////////////////////////////////////////////////////// - - //Nest data by [vars.group] and [vars.id]. - var nestedData = d3 - .nest() - .key(function(d) { - return d[vars.group]; - }) - .key(function(d) { - return d[vars.id]; - }) - .entries(chart.population_data); - - //Calculate number of participants and number of events for each group. - - chart.config.groups.forEach(function(groupObj) { - //count unique participants - var groupVar = chart.config.variables.group; - var groupValue = groupObj.key; - var groupEvents = chart.population_data.filter(function(f) { - return f[groupVar] == groupValue; - }); - groupObj.n = d3 - .set( - groupEvents.map(function(m) { - return m[chart.config.variables.id]; - }) - ) - .values().length; - - //count number of events - groupObj.nEvents = chart.population_event_data.filter(function(f) { - return f[groupVar] == groupValue; - }).length; - }); - } - - var defaultSettings = { - variables: { - id: 'USUBJID', - major: 'AEBODSYS', - minor: 'AEDECOD', - group: 'ARM', - details: null, - filters: [ - { - value_col: 'AESER', - label: 'Serious?', - type: 'event', - start: null - }, - { - value_col: 'AESEV', - label: 'Severity', - type: 'event', - start: null - }, - { - value_col: 'AEREL', - label: 'Relationship', - type: 'event', - start: null - }, - { - value_col: 'AEOUT', - label: 'Outcome', - type: 'event', - start: null - } - ] - }, - variableOptions: null, - groups: null, - defaults: { - placeholderFlag: { - value_col: 'AEBODSYS', - values: ['NA'] - }, - maxPrevalence: 0, - maxGroups: 6, - totalCol: true, - groupCols: true, - diffCol: true, - prefTerms: false, - summarizeBy: 'participant', - webchartsDetailsTable: false, - useVariableControls: true - }, - plotSettings: { - h: 15, - w: 200, - margin: { - left: 40, - right: 40 - }, - diffMargin: { - left: 5, - right: 5 - }, - r: 7 - }, - validation: false - }; - - /*------------------------------------------------------------------------------------------------\ - Filter the raw data per the current filter and group selections. - \------------------------------------------------------------------------------------------------*/ - function setDefaults(chart) { - function errorNote(msg) { - chart.wrap.append('div').attr('class', 'wc-alert').text('Fatal Error: ' + msg); - } - - ///////////////////////////// - // Fill defaults as needed // - ///////////////////////////// - //variables - chart.config.variables = chart.config.variables || {}; - - var variables = ['id', 'major', 'minor', 'group']; - variables.forEach(function(varName) { - chart.config.variables[varName] = - chart.config.variables[varName] || defaultSettings.variables[varName]; - }); - - //details, filters and groups - chart.config.variables.details = - chart.config.variables.details || defaultSettings.variables.details || []; - - chart.config.variables.filters = - chart.config.variables.filters || defaultSettings.variables.filters || []; - - chart.config.groups = chart.config.groups || defaultSettings.groups || []; - - //variableOptions - chart.config.variableOptions = - chart.config.variableOptions || defaultSettings.variableOptions || {}; - - variables.forEach(function(varName) { - //initialize options for each mapping variable - chart.config.variableOptions[varName] = chart.config.variableOptions[varName] - ? chart.config.variableOptions[varName] - : []; - - //confirm that specified variables are included as options - var options = chart.config.variableOptions[varName]; - if (options.indexOf(chart.config.variables[varName]) == -1) { - options.push(chart.config.variables[varName]); - } - - //add "None" option for group dropdown - - if ((varName == 'group') & (options.indexOf('None') == -1)) { - options.push('None'); - } - }); - - //defaults - chart.config.defaults = chart.config.defaults || {}; - var defaults = Object.keys(defaultSettings.defaults); - defaults.forEach(function(dflt) { - if ( - dflt !== 'placeholderFlag' // handle primitive types such as maxPrevalence - ) - chart.config.defaults[dflt] = chart.config.defaults[dflt] !== undefined - ? chart.config.defaults[dflt] - : defaultSettings.defaults[dflt]; - else { - // handle objects such as placeholderFlag - var object = {}; - for (var prop in defaultSettings.defaults[dflt]) { - object[prop] = chart.config.defaults[dflt] !== undefined - ? chart.config.defaults[dflt][prop] !== undefined - ? chart.config.defaults[dflt][prop] - : defaultSettings.defaults[dflt][prop] - : defaultSettings.defaults[dflt][prop]; - } - chart.config.defaults[dflt] = object; - } - }); - - //plot settings - chart.config.plotSettings = chart.config.plotSettings || {}; - var plotSettings = ['h', 'w', 'r', 'margin', 'diffMargin']; - plotSettings.forEach(function(varName) { - chart.config.plotSettings[varName] = - chart.config.plotSettings[varName] || defaultSettings.plotSettings[varName]; - }); - - //////////////////////////////////////////////////////////// - // Convert group levels from string to objects (if needed)// - //////////////////////////////////////////////////////////// - var allGroups = d3 - .set( - chart.raw_data.map(function(d) { - return d[chart.config.variables.group]; - }) - ) - .values(); - chart.config.groups = chart.config.groups - .map(function(d) { - return typeof d == 'string' ? { key: d } : d; - }) - .filter(function(d) { - if (allGroups.indexOf(d.key) === -1) - console.log( - 'Warning: You specified a group level ("' + - d.key + - '") that was not found in the data. It is being ignored.' - ); - return allGroups.indexOf(d.key) > -1; - }); - - //////////////////////////////////////////////////// - // Include all group levels if none are specified // - //////////////////////////////////////////////////// - - var groupsObject = allGroups.map(function(d) { - return { key: d }; - }); - - if (!chart.config.groups || chart.config.groups.length === 0) - chart.config.groups = groupsObject.sort(function(a, b) { - return a.key < b.key ? -1 : a.key > b.key ? 1 : 0; - }); - - ////////////////////////////////////////////////////////////// - //Check that variables specified in settings exist in data. // - ////////////////////////////////////////////////////////////// - for (var x in chart.config.variables) { - var varList = d3.keys(chart.raw_data[0]).concat('data_all'); - - if (varList.indexOf(chart.config.variables[x]) === -1) { - if (chart.config.variables[x] instanceof Array) { - chart.config.variables[x].forEach(function(e) { - var value_col = e instanceof Object ? e.value_col : e; - if (varList.indexOf(value_col) === -1) { - errorNote('Error in variables object.'); - throw new Error( - x + ' variable ' + "('" + e + "') not found in dataset." - ); - } - }); - } else { - errorNote('Error in variables object.'); - throw new Error( - x + - ' variable ' + - "('" + - chart.config.variables[x] + - "') not found in dataset." - ); - } - } - } - - ///////////////////////////////////////////////////////////////////////////////// - //Checks on group columns (if they're being renderered) // - ///////////////////////////////////////////////////////////////////////////////// - if (chart.config.defaults.groupCols) { - //Check that group values defined in settings are actually present in dataset. // - if ( - chart.config.defaults.groupCols & - (chart.config.groups.length > chart.config.defaults.maxGroups) - ) { - var errorText = - 'Too Many Group Variables specified. You specified ' + - chart.config.groups.length + - ', but the maximum supported is ' + - chart.config.defaults.maxGroups + - '.'; - errorNote(errorText); - throw new Error(errorText); - } - - //Set the domain for the color scale based on groups. // - chart.colorScale.domain( - chart.config.groups.map(function(e) { - return e.key; - }) - ); - } - - //make sure either group or total columns are being renderered - if (!chart.config.defaults.groupCols & !chart.config.defaults.totalCol) { - var errorText = - 'No data to render. Make sure at least one of chart.config.defaults.groupCols or chart.config.defaults.totalCol is set to true.'; - errorNote(errorText); - throw new Error(errorText); - } - - //don't render differences if you're not renderering group columns - if (!chart.config.defaults.groupCols) { - chart.config.defaults.diffCol = false; - } - - //hide the total column if only one group is selected - if (chart.config.groups.length == 1) { - chart.config.defaults.totalCol = false; - } - - //set color for total column - if (chart.config.defaults.totalCol) - //Set 'Total' column color to #777. - chart.colorScale.range()[chart.config.groups.length] = '#777'; - } - - /*------------------------------------------------------------------------------------------------\ - Define util object. - \------------------------------------------------------------------------------------------------*/ - - var util = { - calculateDifference: calculateDifference, - addDifferences: addDifferences, - cross: cross, - sort: sort, - fillRow: fillRow, - collapse: collapse, - json2csv: json2csv, - prepareData: prepareData, - setDefaults: setDefaults - }; - - /*------------------------------------------------------------------------------------------------\ - Call functions to collapse the raw data using the selected categories and create the summary - table. - \------------------------------------------------------------------------------------------------*/ - - function init$7(chart) { - //convinience mappings - var vars = chart.config.variables; - - ///////////////////////////////////////////////////////////////// - // Prepare the data for charting - ///////////////////////////////////////////////////////////////// - chart.data = {}; - - //Create a dataset nested by [ chart.config.variables.group ] and [ chart.config.variables.id ]. - chart.data.any = util.cross( - chart.population_event_data, - chart.config.groups, - vars['id'], - 'All', - 'All', - vars['group'], - chart.config.groups - ); - - //Create a dataset nested by [ chart.config.variables.major ], [ chart.config.variables.group ], and - //[ chart.config.variables.id ]. - chart.data.major = util.cross( - chart.population_event_data, - chart.config.groups, - vars['id'], - vars['major'], - 'All', - vars['group'], - chart.config.groups - ); - - //Create a dataset nested by [ chart.config.variables.major ], [ chart.config.variables.minor ], - //[ chart.config.variables.group ], and [ chart.config.variables.id ]. - chart.data.minor = util.cross( - chart.population_event_data, - chart.config.groups, - vars['id'], - vars['major'], - vars['minor'], - vars['group'], - chart.config.groups - ); - - //Add a 'differences' object to each row. - chart.data.major = util.addDifferences(chart.data.major, chart.config.groups); - chart.data.minor = util.addDifferences(chart.data.minor, chart.config.groups); - chart.data.any = util.addDifferences(chart.data.any, chart.config.groups); - - //Sort the data based by maximum prevelence. - chart.data.major = chart.data.major.sort(util.sort.maxPer); - chart.data.minor.forEach(function(major) { - major.values.sort(function(a, b) { - var max_a = - d3.sum( - a.values.map(function(group) { - return group.values.n; - }) - ) / - d3.sum( - a.values.map(function(group) { - return group.values.tot; - }) - ); - var max_b = - d3.sum( - b.values.map(function(group) { - return group.values.n; - }) - ) / - d3.sum( - b.values.map(function(group) { - return group.values.tot; - }) - ); - var diff = max_b - max_a; - - return diff ? diff : a.key < b.key ? -1 : 1; - }); - }); - - ///////////////////////////////////////////////////////////////// - // Allow the user to download a csv of the current view - ///////////////////////////////////////////////////////////////// - // - //Output the data if the validation setting is flagged. - if (chart.config.validation) chart.data.CSVarray = util.json2csv(chart); - - ///////////////////////////////////// - // Draw the summary table headers. - ///////////////////////////////////// - //Check to make sure there is some data - if (!chart.data.major.length) { - chart.wrap - .select('.SummaryTable') - .append('div') - .attr('class', 'wc-alert') - .text( - 'Error: No AEs found for the current filters. Update the filters to see results.' - ); - throw new Error('No data found in the column specified for major category. '); - } - - var tab = chart.wrap.select('.SummaryTable').append('table'); - var nGroups = chart.config.groups.length; - var header1 = tab.append('thead').append('tr'); - - //Expand/collapse control column header - header1.append('th').attr('rowspan', 2); - - //Category column header - header1.append('th').attr('rowspan', 2).text('Category'); - - //Group column headers - if (chart.config.defaults.groupCols) - header1.append('th').attr('colspan', nGroups).text('Groups'); - - //Total column header - if (chart.config.defaults.totalCol) header1.append('th').text(''); - - //Graphical AE rates column header - header1.append('th').text('AE Rate by group'); - - //Group differences column header - var groupHeaders = chart.config.defaults.groupCols ? chart.config.groups : []; - if (chart.config.defaults.totalCol) { - groupHeaders = groupHeaders.concat({ - key: 'Total', - n: d3.sum(chart.config.groups, function(d) { - return d.n; - }), - nEvents: d3.sum(chart.config.groups, function(d) { - return d.nEvents; - }) - }); - } - - var header2 = tab.select('thead').append('tr'); - header2 - .selectAll('td.values') - .data(groupHeaders) - .enter() - .append('th') - .html(function(d) { - return ( - '' + - d.key + - '' + - '0)for(u=-1;++u
' +
- 'PALT shows promise in predicting the percentage hepatocyte loss on the basis of the maximum value and the AUC of serum ALT observed during a DILI event. For more details see A Rapid Method to Estimate Hepatocyte Loss Due to Drug-Induced Liver Injury by Chung et al.';
-
- var obj = {
- value: p_alt,
- text_value: p_alt_rounded,
- values: alt_values,
- note: note,
- components: {
- peak: alt_peak,
- auc: alt_auc
- }
- };
-
- return obj;
- } else {
- return null; //if no alt values are found
- }
- }
-
- //Converts a one record per measure data object to a one record per participant objects
- function flattenData() {
- var chart = this;
- var config = this.config;
-
- //////////////////////////////////////////////
- //make a data set with one object per ID
- /////////////////////////////////////////////
-
- //get a list of columns to flatten
- config.flat_cols = getFlatCols.call(this);
-
- //get maximum values for each measure type
- var flat_data = d3
- .nest()
- .key(function (f) {
- return f[config.id_col];
- })
- .rollup(function (d) {
- return getMaxValues.call(chart, d);
- })
- .entries(
- this.imputed_data.filter(function (f) {
- return f.key_measure;
- })
- );
-
- chart.dropped_participants = flat_data
- .filter(function (f) {
- return f.values.drop_participant;
- })
- .map(function (d) {
- return {
- id: d.key,
- drop_reason: d.values.drop_reason,
- allrecords: chart.initial_data.filter(function (f) {
- return f[config.id_col] == d.key;
- })
- };
- });
- var flat_data = flat_data
- .filter(function (f) {
- return !f.values.drop_participant;
- })
- .map(function (m) {
- m.values[config.id_col] = m.key;
-
- //link the raw data to the flattened object
- var allMatches = chart.imputed_data.filter(function (f) {
- return f[config.id_col] == m.key;
- });
- m.values.raw = allMatches;
-
- m.values.p_alt = config.calculate_palt ? calculatePalt.call(chart, m) : null;
- return m.values;
- });
- return flat_data;
- }
-
- function setLegendLabel() {
- //change the legend label to match the group variable
- //or hide legend if group = NONE
- this.config.legend.label =
- this.config.color_by !== 'NONE'
- ? this.config.group_cols[
- this.config.group_cols
- .map(function (group) {
- return group.value_col;
- })
- .indexOf(this.config.color_by)
- ].label
- : '';
- }
-
- function showMissingDataWarning() {
- var chart = this;
- var config = chart.config;
-
- if (config.debug) {
- //confirm participants are only dropped once (?!)
- var unique_dropped_participants = d3
- .set(
- this.dropped_participants.map(function (m) {
- return m.id;
- })
- )
- .values().length;
- console.warn(
- 'Of ' +
- this.dropped_participants.length +
- ' dropped participants, ' +
- unique_dropped_participants +
- ' are unique.'
- );
- console.warn(this.dropped_participants);
- }
-
- chart.messages.remove(null, 'droppedPts', chart.messages); //remove message from previous render
- if (this.dropped_participants.length > 0) {
- var warningText =
- this.dropped_participants.length +
- ' participants are not plotted. They likely have invalid or missing data for key variables in the current chart. Click here to download a csv with a brief explanation of why each participant was not plotted.';
-
- this.messages.add(warningText, 'caution', 'droppedPts', this.messages, function () {
- //custom callback to activate the droppedRows download
- d3.select(this)
- .select('a.ptDownload')
- .style('color', 'blue')
- .style('text-decoration', 'underline')
- .style('cursor', 'pointer')
- .datum(chart.dropped_participants)
- .on('click', function (d) {
- var cols = ['id', 'drop_reason'];
- downloadCSV.call(this, d, cols, 'eDishDroppedParticipants');
- });
- });
- }
- }
-
- function dropMissingValues() {
- var chart = this;
- var config = this.config;
- //drop records with missing or invalid (negative) values
- var missing_count = d3.sum(this.raw_data, function (f) {
- return f[config.x.column] <= 0 || f[config.y.column] <= 0;
- });
-
- if (missing_count > 0) {
- this.raw_data = this.raw_data.map(function (d) {
- d.nonPositiveFlag = d[config.x.column] <= 0 || d[config.y.column] <= 0;
- var type = config.display == 'relative_uln' ? 'eDish' : 'mDish';
- // generate an informative reason the participant was dropped
- var dropText =
- type +
- ' values could not be generated for ' +
- config.x.column +
- ' or ' +
- config.y.column +
- '. ';
-
- // x type is mdish and baseline is missing
- if ((type == 'mDish') & !d[config.x.column + '_baseline_absolute']) {
- dropText = dropText + 'Baseline for ' + config.x.column + ' is missing. ';
- }
-
- // y type is mdish and baseline is missing
- if ((type == 'mDish') & !d[config.y.column + '_baseline_absolute']) {
- dropText = dropText + 'Baseline for ' + config.y.column + ' is missing. ';
- }
-
- d.drop_reason = d.nonPositiveFlag ? dropText : '';
- return d;
- });
-
- this.dropped_participants = d3.merge([
- this.dropped_participants,
- this.raw_data
- .filter(function (f) {
- return f.nonPositiveFlag;
- })
- .map(function (m) {
- return { id: m[config.id_col], drop_reason: m.drop_reason };
- })
- ]);
-
- this.dropped_participants.map(function (m) {
- m.raw = chart.initial_data.filter(function (f) {
- return f[config.id_col] == m.id;
- });
- });
- }
-
- this.raw_data = this.raw_data.filter(function (f) {
- return !f.nonPositiveFlag;
- });
- showMissingDataWarning.call(this);
- }
-
- function onPreprocess() {
- updateAxisSettings.call(this); //update axis label based on display type
- updateControlCutpointLabels.call(this); //update cutpoint control labels given x- and y-axis variables
- this.raw_data = flattenData.call(this); //convert from visit-level data to participant-level data
- setMaxRRatio.call(this);
- setLegendLabel.call(this); //update legend label based on group variable
- dropMissingValues.call(this);
- }
-
- function onDataTransform() { }
-
- function updateQuadrantData() {
- var chart = this;
- var config = this.config;
-
- //add "eDISH_quadrant" column to raw_data
- var x_var = this.config.x.column;
- var y_var = this.config.y.column;
-
- var x_cut = this.config.cuts[x_var][config.display];
- var y_cut = this.config.cuts[y_var][config.display];
-
- this.filtered_data.forEach(function (d) {
- var x_cat = d[x_var] >= x_cut ? 'xHigh' : 'xNormal';
- var y_cat = d[y_var] >= y_cut ? 'yHigh' : 'yNormal';
- d['eDISH_quadrant'] = x_cat + ':' + y_cat;
- });
-
- //update Quadrant data
- config.quadrants.forEach(function (quad) {
- quad.count = chart.filtered_data.filter(function (d) {
- return d.eDISH_quadrant == quad.dataValue;
- }).length;
- quad.total = chart.filtered_data.length;
- quad.percent = d3.format('0.1%')(quad.count / quad.total);
- });
- }
-
- function setDomain(dimension) {
- var config = this.config;
- var domain = this[dimension].domain();
- var measure = config[dimension].column;
- var measure_long = config.measure_values[measure];
- var cut = config.cuts[measure][config.display];
- var values = this.imputed_data
- .filter(function (f) {
- return f[config.measure_col] == measure_long;
- })
- .map(function (m) {
- return +m[config.display];
- })
- .filter(function (m) {
- return m > 0;
- })
- .sort(function (a, b) {
- return a - b;
- });
- var val_extent = d3.extent(values);
-
- //make sure the domain contains the cut point and the max possible value for the measure
- domain[1] = d3.max([domain[1], cut * 1.01, val_extent[1]]);
-
- // make sure the domain lower limit captures all of the raw Values
- if (this.config[dimension].type == 'linear') {
- // just use the lower limit of 0 for continuous
- domain[0] = 0;
- } else if (this.config[dimension].type == 'log') {
- // use the smallest raw value for a log axis
-
- var minValue = val_extent[0];
-
- if (minValue < domain[0]) {
- domain[0] = minValue;
- }
-
- //throw a warning if the domain is > 0 if using log scale
- if (this[dimension].type == 'log' && domain[0] <= 0) {
- console.warn(
- "Can't draw a log " + dimension + '-axis because there are values <= 0.'
- );
- }
- }
- this[dimension + '_dom'] = domain;
- }
-
- function clearVisitPath() {
- this.visitPath.selectAll('*').remove();
- }
-
- function clearParticipantHeader() {
- this.participantDetails.header.selectAll('*').remove(); //clear participant header
- }
-
- function hideMeasureTable() {
- this.measureTable.draw([]);
- this.measureTable.wrap.selectAll('*').style('display', 'none');
- }
-
- function clearRugs(axis) {
- this[axis + '_rug'].selectAll('*').remove();
- }
-
- function formatPoints() {
- var chart = this;
- var config = this.config;
- var points = this.svg
- .select('g.point-supergroup.mark1')
- .selectAll('g.point')
- .select('circle');
-
- points
- .attr('stroke', function (d) {
- var disabled = d3.select(this).classed('disabled');
- var raw = d.values.raw[0],
- pointColor = chart.colorScale(raw[config.color_by]);
- return disabled ? '#ccc' : pointColor;
- })
- .attr('fill', function (d) {
- var disabled = d3.select(this).classed('disabled');
- var raw = d.values.raw[0],
- pointColor = chart.colorScale(raw[config.color_by]);
- return disabled ? 'white' : pointColor;
- })
- .attr('stroke-width', 1)
- .style('clip-path', null);
- }
-
- function clearParticipantDetails() {
- var chart = this;
- var config = this.config;
- var points = this.svg
- .select('g.point-supergroup.mark1')
- .selectAll('g.point')
- .select('circle');
-
- points.classed('disabled', false);
-
- // update
- chart.participantsSelected = [];
- chart.events.participantsSelected.data = chart.participantsSelected;
- chart.wrap.node().dispatchEvent(chart.events.participantsSelected);
-
- // remove/hide details
- this.config.quadrants.table.wrap.style('display', null);
- clearVisitPath.call(this); //remove path
- clearParticipantHeader.call(this);
- clearRugs.call(this, 'x'); //clear rugs
- clearRugs.call(this, 'y');
- hideMeasureTable.call(this); //remove the detail table
- formatPoints.call(this);
-
- this.participantDetails.wrap.selectAll('*').style('display', 'none');
- }
-
- function updateFilterLabel() {
- if (this.controls.filter_numerator) {
- this.controls.filter_numerator.text(this.filtered_data.length);
- }
- }
-
- function setCutpointMinimums() {
- var chart = this;
- var config = this.config;
- var lower_limits = {
- x: chart['x_dom'][0],
- y: chart['y_dom'][0]
- };
-
- //Make sure cutpoint isn't below lower domain - Comes in to play when changing from log to linear axes
- Object.keys(lower_limits).forEach(function (dimension) {
- var measure = config[dimension].column;
- var current_cut = config.cuts[measure][config.display];
- var min = lower_limits[dimension];
- if (current_cut < min) {
- config.cuts[measure][config.display] = min;
- chart.controls.wrap
- .selectAll('div.control-group')
- .filter(function (f) {
- return f.description
- ? f.description.toLowerCase() == dimension + '-axis reference line'
- : false;
- })
- .select('input')
- .node().value = min;
- }
- });
-
- //Update cut point controls
- var controlWraps = this.controls.wrap
- .selectAll('.control-group')
- .filter(function (d) {
- return /.-axis Reference Line/i.test(d.description);
- })
- .attr('min', function (d) {
- return lower_limits[d.description.split('-')[0]];
- });
-
- controlWraps.select('input').on('change', function (d) {
- var dimension = d.description.split('-')[0].toLowerCase();
- var min = chart[dimension + '_dom'][0];
- var input = d3.select(this);
-
- //Prevent a cutpoint less than the lower domain.
- if (input.property('value') < min) input.property('value', min);
-
- //Update chart setting.
- var measure = config[dimension].column;
- config.cuts[measure][config.display] = input.property('value');
- chart.draw();
- });
- }
-
- function syncCutpoints() {
- var chart = this;
- var config = this.config;
-
- //check to see if the cutpoint used is current
- if (
- config.cuts.x != config.x.column ||
- config.cuts.y != config.y.column ||
- config.cuts.display != config.display
- ) {
- // if not, update it!
-
- // track the current cut point variables
- config.cuts.x = config.x.column;
- config.cuts.y = config.y.column;
- config.cuts.display = config.display;
-
- // update the cutpoint shown in the control
- config.cuts.display_change = false; //reset the change flag;
- var dimensions = ['x', 'y'];
- dimensions.forEach(function (dimension) {
- //change the control to point at the correct cut point
- var dimInput = chart.controls.wrap
- .selectAll('div.control-group')
- .filter(function (f) {
- return f.description
- ? f.description.toLowerCase() == dimension + '-axis reference line'
- : false;
- })
- .select('input');
-
- dimInput.node().value = config.cuts[config[dimension].column][config.display];
-
- //don't think this actually changes functionality, but nice to have it accurate just in case
- dimInput.option =
- 'settings.cuts.' + [config[dimension].column] + '.' + [config.display];
- });
- }
- }
-
- function hideEmptyChart() {
- var emptyChart = this.filtered_data.length == 0;
- this.wrap.style('display', emptyChart ? 'none' : 'inline-block');
- this.emptyChartWarning.style('display', emptyChart ? 'inline-block' : 'none');
- }
-
- function updateStudyDayControl() {
- var chart = this;
- var config = this.config;
-
- // cancel the animation (if any is running)
- var activeAnimation = chart.controls.studyDayPlayButton.datum().state == 'stop';
- if (activeAnimation) {
- chart.svg.transition().duration(0);
- }
-
- // hide study day control if viewing max values
- chart.controls.studyDayControlWrap.style('display', config.plot_max_values ? 'none' : null);
-
- //set the status of the play button
- if (config.plot_day >= chart.controls.studyDayRange[1]) {
- chart.controls.studyDayPlayButton.datum({ state: 'restart' });
- chart.controls.studyDayPlayButton.html('↺');
- } else {
- chart.controls.studyDayPlayButton.datum({ state: 'play' });
- chart.controls.studyDayPlayButton.html('►');
- }
-
- //update the study day control label with the currently selected values
- var currentValue = chart.controls.studyDayControlWrap.select('input').property('value');
- chart.controls.studyDayControlWrap
- .select('span.wc-control-label')
- .html('Showing data from: Day ' + currentValue + '')
- .select('strong')
- .style('color', 'blue');
- }
-
- function onDraw() {
- //show/hide the study day controls
- updateStudyDayControl.call(this);
-
- //clear participant Details (if they exist)
- clearParticipantDetails.call(this);
-
- //get correct cutpoint for the current view
- syncCutpoints.call(this);
-
- //update domains to include cut lines
- setDomain.call(this, 'x');
- setDomain.call(this, 'y');
-
- //Set update cutpoint interactivity
- setCutpointMinimums.call(this);
-
- //Classify participants in to eDISH quadrants
- updateQuadrantData.call(this);
-
- //update the count in the filter label
- updateFilterLabel.call(this);
- hideEmptyChart.call(this);
- }
-
- //draw marginal rug for visit-level measures
- function drawRugs(d, axis) {
- var chart = this;
- var config = this.config;
-
- //get matching measures
- var allMatches = d.values.raw[0].raw;
- var measure = config.measure_values[config[axis].column];
- var matches = allMatches.filter(function (f) {
- return f[config.measure_col] == measure;
- });
-
- //draw the rug
- var min_value = axis == 'x' ? chart.y.domain()[0] : chart.x.domain()[0];
- var rugs = chart[axis + '_rug']
- .selectAll('text')
- .data(matches)
- .enter()
- .append('text')
- .attr({
- class: 'rug-tick',
- x: function x(di) {
- return axis === 'x' ? chart.x(di[config.display]) : chart.x(min_value);
- },
- y: function y(di) {
- return axis === 'y' ? chart.y(di[config.display]) : chart.y(min_value);
- },
- dy: axis === 'y' ? 4 : 0,
- 'text-anchor': axis === 'y' ? 'end' : null,
- 'alignment-baseline': axis === 'x' ? 'hanging' : null,
- 'font-size': axis === 'x' ? '6px' : null,
- stroke: function stroke(di) {
- return chart.colorScale(di[config.color_by]);
- },
- 'stroke-width': function strokeWidth(di) {
- return di[config.display] === d.values[axis] ? '3px' : '1px';
- }
- })
- .text(function (d) {
- return axis === 'x' ? '|' : '–';
- });
-
- //Add tooltips to rugs.
- rugs.append('svg:title').text(function (d) {
- return (
- d[config.measure_col] +
- '=' +
- d3.format('.2f')(d.absolute) +
- ' (' +
- d3.format('.2f')(d.relative_uln) +
- ' xULN) @ ' +
- d[config.visit_col] +
- '/Study Day ' +
- d[config.studyday_col]
- );
- });
- }
-
- function addPointMouseover() {
- var chart = this;
- var config = this.config;
- var points = this.marks[0].circles;
-
- //add event listener to all participant level points
- points
- .filter(function (d) {
- var disabled = d3.select(this).classed('disabled');
- return !disabled;
- })
- .on('mouseover', function (d) {
- //disable mouseover when highlights (onClick) are visible
- var disabled = d3.select(this).classed('disabled');
- if (!disabled) {
- //clear previous mouseover if any
- points.attr('stroke-width', 1);
- clearRugs.call(chart, 'x');
- clearRugs.call(chart, 'y');
-
- //draw the rugs
- d3.select(this).attr('stroke-width', 3);
- drawRugs.call(chart, d, 'x');
- drawRugs.call(chart, d, 'y');
- }
- });
- }
-
- function drawVisitPath(d) {
- var chart = this;
- var config = chart.config;
-
- var allMatches = d.values.raw[0].raw;
- var x_measure = config.measure_values[config.x.column];
- var y_measure = config.measure_values[config.y.column];
- var matches = allMatches.filter(function (f) {
- return f[config.measure_col] == x_measure || f[config.measure_col] == y_measure;
- });
-
- //get coordinates by visit
- var visits = d3
- .set(
- matches.map(function (m) {
- return m[config.studyday_col];
- })
- )
- .values();
- var visit_data = visits
- .map(function (m) {
- var visitObj = {};
- visitObj.studyday = +m;
- visitObj.visit = config.visit_col
- ? matches.filter(function (f) {
- return f[config.studyday_col] == m;
- })[0][config.visit_col]
- : null;
- visitObj.visitn = config.visitn_col
- ? matches.filter(function (f) {
- return f[config.studyday_col] == m;
- })[0][config.visitn_col]
- : null;
- visitObj[config.color_by] = matches[0][config.color_by];
-
- //get x coordinate
- var x_match = matches
- .filter(function (f) {
- return f[config.studyday_col] == m;
- })
- .filter(function (f) {
- return f[config.measure_col] == x_measure;
- });
-
- if (x_match.length) {
- visitObj.x = x_match[0][config.display];
- visitObj.xMatch = x_match[0];
- } else {
- visitObj.x = null;
- visitObj.xMatch = null;
- }
-
- //get y coordinate
- var y_match = matches
- .filter(function (f) {
- return f[config.studyday_col] == m;
- })
- .filter(function (f) {
- return f[config.measure_col] == y_measure;
- });
- if (y_match.length) {
- visitObj.y = y_match[0][config.display];
- visitObj.yMatch = y_match[0];
- } else {
- visitObj.y = null;
- visitObj.yMatch = null;
- }
-
- //get rRatio
- var rRatio_match = d.values.raw[0].rRatio_raw.filter(function (f) {
- return f[config.studyday_col] == m;
- });
- visitObj.rRatio = rRatio_match.length ? rRatio_match[0].rRatio : null;
-
- return visitObj;
- })
- .sort(function (a, b) {
- return a.studyday - b.studyday;
- })
- .filter(function (f) {
- return (f.x > 0) & (f.y > 0);
- });
-
- //draw the path
- var myLine = d3.svg
- .line()
- .x(function (d) {
- return chart.x(d.x);
- })
- .y(function (d) {
- return chart.y(d.y);
- });
-
- chart.visitPath.selectAll('*').remove();
- chart.visitPath.moveToFront();
-
- var path = chart.visitPath
- .append('path')
- .attr('class', 'participant-visits')
- .datum(visit_data)
- .attr('d', myLine)
- .attr('stroke', function (d) {
- return chart.colorScale(matches[0][config.color_by]);
- })
- .attr('stroke-width', '2px')
- .attr('fill', 'none');
-
- //Little trick for animating line drawing
- var totalLength = path.node().getTotalLength();
- path.attr('stroke-dasharray', totalLength + ' ' + totalLength)
- .attr('stroke-dashoffset', totalLength)
- .transition()
- .duration(2000)
- .ease('linear')
- .attr('stroke-dashoffset', 0);
-
- //draw visit points
- var visitPoints = chart.visitPath
- .selectAll('g.visit-point')
- .data(visit_data)
- .enter()
- .append('g')
- .attr('class', 'visit-point');
-
- visitPoints
- .append('circle')
- .attr('class', 'participant-visits')
- .attr('r', 0)
- .attr('stroke', function (d) {
- return chart.colorScale(d[config.color_by]);
- })
- .attr('stroke-width', 1)
- .attr('cx', function (d) {
- return chart.x(d.x);
- })
- .attr('cy', function (d) {
- return chart.y(d.y);
- })
- .attr('fill', function (d) {
- return chart.colorScale(d[config.color_by]);
- })
- .attr('fill-opacity', 0)
- .transition()
- .delay(2000)
- .duration(200)
- .attr('r', 4);
-
- //custom titles for points on mouseover
- visitPoints.append('title').text(function (d) {
- var xvar = config.x.column;
- var yvar = config.y.column;
-
- var studyday_label = 'Study day: ' + d.studyday + '\n',
- visitn_label = d.visitn ? 'Visit Number: ' + d.visitn + '\n' : '',
- visit_label = d.visit ? 'Visit: ' + d.visit + '\n' : '',
- x_label = config.x.label + ': ' + d3.format('0.3f')(d.x) + '\n',
- y_label = config.y.label + ': ' + d3.format('0.3f')(d.y),
- rRatio_label = d.rRatio ? '\nR Ratio: ' + d3.format('0.2f')(d.rRatio) : '';
-
- return studyday_label + visit_label + visitn_label + x_label + y_label + rRatio_label;
- });
- }
-
- function makeNestedData(d) {
- var chart = this;
- var config = chart.config;
- var allMatches = d.values.raw[0].raw;
-
- var ranges = d3
- .nest()
- .key(function (d) {
- return d[config.measure_col];
- })
- .rollup(function (d) {
- var vals = d
- .map(function (m) {
- return m[config.value_col];
- })
- .sort(function (a, b) {
- return a - b;
- });
- var lower_extent = d3.quantile(vals, config.measureBounds[0]),
- upper_extent = d3.quantile(vals, config.measureBounds[1]);
- return [lower_extent, upper_extent];
- })
- .entries(chart.initial_data);
-
- //make nest by measure
- var nested = d3
- .nest()
- .key(function (d) {
- return d[config.measure_col];
- })
- .rollup(function (d) {
- var measureObj = {};
- measureObj.eDish = chart;
- measureObj.key = d[0][config.measure_col];
- measureObj.raw = d;
- measureObj.values = d.map(function (d) {
- return +d[config.value_col];
- });
- measureObj.max = +d3.format('0.2f')(d3.max(measureObj.values));
- measureObj.min = +d3.format('0.2f')(d3.min(measureObj.values));
- measureObj.median = +d3.format('0.2f')(d3.median(measureObj.values));
- measureObj.n = measureObj.values.length;
- measureObj.spark = 'spark!';
- measureObj.population_extent = ranges.find(function (f) {
- return measureObj.key == f.key;
- }).values;
- var hasColor =
- chart.spaghetti.colorScale.domain().indexOf(d[0][config.measure_col]) > -1;
- measureObj.color = hasColor
- ? chart.spaghetti.colorScale(d[0][config.measure_col])
- : 'black';
- measureObj.spark_data = d.map(function (m) {
- var obj = {
- id: m[config.id_col],
- lab: m[config.measure_col],
- visit: config.visit_col ? m[config.visit_col] : null,
- visitn: config.visitn_col ? +m[config.visitn_col] : null,
- studyday: +m[config.studyday_col],
- value: +m[config.value_col],
- lln: config.normal_col_low ? +m[config.normal_col_low] : null,
- uln: +m[config.normal_col_high],
- population_extent: measureObj.population_extent,
- outlier_low: config.normal_col_low
- ? +m[config.value_col] < +m[config.normal_col_low]
- : null,
- outlier_high: +m[config.value_col] > +m[config.normal_col_high]
- };
- obj.outlier = obj.outlier_low || obj.outlier_high;
- return obj;
- });
- return measureObj;
- })
- .entries(allMatches);
-
- var nested = nested
- .map(function (m) {
- return m.values;
- })
- .sort(function (a, b) {
- var a_order = Object.keys(config.measure_values)
- .map(function (e) {
- return config.measure_values[e];
- })
- .indexOf(a.key);
- var b_order = Object.keys(config.measure_values)
- .map(function (e) {
- return config.measure_values[e];
- })
- .indexOf(b.key);
- return b_order - a_order;
- });
- return nested;
- }
-
- function addSparkLines(d) {
- if (this.data.raw.length > 0) {
- //don't try to draw sparklines if the table is empty
- this.tbody
- .selectAll('tr')
- .style('background', 'none')
- .style('border-bottom', '.5px solid black')
- .each(function (row_d) {
- //Spark line cell
- var cell = d3
- .select(this)
- .select('td.spark')
- .classed('minimized', true)
- .text(''),
- toggle = cell
- .append('span')
- .html('▽')
- .style('cursor', 'pointer')
- .style('color', '#999')
- .style('vertical-align', 'middle'),
- width = 100,
- height = 25,
- offset = 4,
- overTime = row_d.spark_data.sort(function (a, b) {
- return +a.studyday - +b.studyday;
- }),
- color = row_d.color;
-
- var x = d3.scale
- .linear()
- .domain(
- d3.extent(overTime, function (m) {
- return m.studyday;
- })
- )
- .range([offset, width - offset]);
-
- //y-domain includes 99th population percentile + any participant outliers
- var y_min = d3.min(d3.merge([row_d.values, row_d.population_extent])) * 0.99;
- var y_max = d3.max(d3.merge([row_d.values, row_d.population_extent])) * 1.01;
- var y = d3.scale
- .linear()
- .domain([y_min, y_max])
- .range([height - offset, offset]);
-
- //render the svg
- var svg = cell
- .append('svg')
- .attr({
- width: width,
- height: height
- })
- .append('g');
-
- //draw the normal range polygon ULN and LLN
- var upper = overTime.map(function (m) {
- return { studyday: m.studyday, value: m.uln };
- });
- var lower = overTime
- .map(function (m) {
- return { studyday: m.studyday, value: m.lln };
- })
- .reverse();
- var normal_data = d3.merge([upper, lower]).filter(function (m) {
- return m.value;
- });
-
- var drawnormal = d3.svg
- .line()
- .x(function (d) {
- return x(d.studyday);
- })
- .y(function (d) {
- return y(d.value);
- });
-
- var normalpath = svg
- .append('path')
- .datum(normal_data)
- .attr({
- class: 'normalrange',
- d: drawnormal,
- fill: '#eee',
- stroke: 'none'
- });
-
- //draw lines at the population guidelines
- svg.selectAll('lines.guidelines')
- .data(row_d.population_extent)
- .enter()
- .append('line')
- .attr('class', 'guidelines')
- .attr('x1', 0)
- .attr('x2', width)
- .attr('y1', function (d) {
- return y(d);
- })
- .attr('y2', function (d) {
- return y(d);
- })
- .attr('stroke', '#ccc')
- .attr('stroke-dasharray', '2 2');
-
- //draw the sparkline
- var draw_sparkline = d3.svg
- .line()
- .interpolate('cardinal')
- .x(function (d) {
- return x(d.studyday);
- })
- .y(function (d) {
- return y(d.value);
- });
- var sparkline = svg
- .append('path')
- .datum(overTime)
- .attr({
- class: 'sparkLine',
- d: draw_sparkline,
- fill: 'none',
- stroke: color
- });
-
- //draw outliers
- var outliers = overTime.filter(function (f) {
- return f.outlier;
- });
- var outlier_circles = svg
- .selectAll('circle.outlier')
- .data(outliers)
- .enter()
- .append('circle')
- .attr('class', 'circle outlier')
- .attr('cx', function (d) {
- return x(d.studyday);
- })
- .attr('cy', function (d) {
- return y(d.value);
- })
- .attr('r', '2px')
- .attr('stroke', color)
- .attr('fill', color);
- });
- }
- }
-
- function insertAfter(newNode, referenceNode) {
- referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
- }
-
- var defaultSettings = {
- max_width: 800,
- aspect: 4,
- x: {
- column: 'studyday',
- type: 'linear',
- label: 'Study Day'
- },
- y: {
- column: 'value',
- type: 'linear',
- label: '',
- format: '.1f'
- },
- marks: [
- {
- type: 'line',
- per: ['lab']
- },
- {
- type: 'circle',
- radius: 4,
- per: ['lab', 'studyday'] //,
- // values: { outlier: [true] },
- // attributes: {
- // 'fill-opacity': 1
- // }
- }
- ],
- margin: { top: 20 },
- gridlines: 'x',
- colors: []
- };
-
- function setDomain$1(d) {
- //y-domain includes 99th population percentile + any participant outliers
- var raw_values = this.raw_data.map(function (m) {
- return m.value;
- });
- var population_extent = this.raw_data[0].population_extent;
- var y_min = d3.min(d3.merge([raw_values, population_extent])) * 0.99;
- var y_max = d3.max(d3.merge([raw_values, population_extent])) * 1.01;
- this.y.domain([y_min, y_max]);
- this.y_dom = [y_min, y_max];
- }
-
- function drawPopulationExtent() {
- var lineChart = this;
- this.svg
- .selectAll('line.guidelines')
- .data(lineChart.raw_data[0].population_extent)
- .enter()
- .append('line')
- .attr('class', 'guidelines')
- .attr('x1', 0)
- .attr('x2', lineChart.plot_width)
- .attr('y1', function (d) {
- return lineChart.y(d);
- })
- .attr('y2', function (d) {
- return lineChart.y(d);
- })
- .attr('stroke', '#ccc')
- .attr('stroke-dasharray', '2 2');
- }
-
- function drawNormalRange() {
- var lineChart = this;
- var upper = this.raw_data.map(function (m) {
- return { studyday: m.studyday, value: m.uln };
- });
- var lower = this.raw_data
- .map(function (m) {
- return { studyday: m.studyday, value: m.lln };
- })
- .reverse();
- var normal_data = d3.merge([upper, lower]).filter(function (f) {
- return f.value || f.value == 0;
- });
- var drawnormal = d3.svg
- .line()
- .x(function (d) {
- return lineChart.x(d.studyday);
- })
- .y(function (d) {
- return lineChart.y(d.value);
- });
- var normalpath = this.svg
- .append('path')
- .datum(normal_data)
- .attr({
- class: 'normalrange',
- d: drawnormal,
- fill: '#eee',
- stroke: 'none'
- });
- normalpath.moveToBack();
- }
-
- function addPointTitles() {
- var config = this.edish.config;
- var points = this.marks[1].circles;
- points.select('title').remove();
- points.append('title').text(function (d) {
- var raw = d.values.raw[0];
- var xvar = config.x.column;
- var yvar = config.y.column;
- var studyday_label = 'Study day: ' + raw.studyday + '\n',
- visitn_label = raw.visitn ? 'Visit Number: ' + raw.visitn + '\n' : '',
- visit_label = raw.visit ? 'Visit: ' + raw.visit + '\n' : '',
- lab_label = raw.lab + ': ' + d3.format('0.3f')(raw.value);
- return studyday_label + visit_label + visitn_label + lab_label;
- });
- }
-
- function updatePointFill() {
- var points = this.marks[1].circles;
- points.attr('fill-opacity', function (d) {
- var outlier = d.values.raw[0].outlier;
- return outlier ? 1 : 0;
- });
- }
-
- function init$2(d, edish) {
- //layout the new cells on the DOM (slightly easier than using D3)
- var summaryRow_node = this.parentNode;
- var chartRow_node = document.createElement('tr');
- var chartCell_node = document.createElement('td');
- insertAfter(chartRow_node, summaryRow_node);
- chartRow_node.appendChild(chartCell_node);
-
- //update the row styles
- d3.select(chartRow_node)
- .style('background', 'none')
- .style('border-bottom', '0.5px solid black');
-
- //layout the svg with D3
- var cellCount = d3.select(summaryRow_node).selectAll('td')[0].length;
- var chartCell = d3.select(chartCell_node).attr('colspan', cellCount);
-
- //draw the chart
- defaultSettings.colors = [d.color];
- var lineChart = webcharts.createChart(chartCell_node, defaultSettings);
- lineChart.on('draw', function () {
- setDomain$1.call(this);
- });
- lineChart.edish = edish;
- lineChart.on('resize', function () {
- drawPopulationExtent.call(this);
- drawNormalRange.call(this);
- addPointTitles.call(this);
- updatePointFill.call(this);
- });
- lineChart.init(d.spark_data);
- lineChart.row = chartRow_node;
- return lineChart;
- }
-
- function addSparkClick() {
- var edish = this.edish;
- if (this.data.raw.length > 0) {
- this.tbody
- .selectAll('tr')
- .select('td.spark')
- .on('click', function (d) {
- if (d3.select(this).classed('minimized')) {
- d3.select(this).classed('minimized', false);
- d3.select(this.parentNode).style('border-bottom', 'none');
-
- this.lineChart = init$2.call(this, d, edish);
- d3.select(this)
- .select('svg')
- .style('display', 'none');
-
- d3.select(this)
- .select('span')
- .html('△ Minimize Chart');
- } else {
- d3.select(this).classed('minimized', true);
-
- d3.select(this.parentNode).style('border-bottom', '0.5px solid black');
-
- d3.select(this)
- .select('span')
- .html('▽');
-
- d3.select(this)
- .select('svg')
- .style('display', null);
-
- d3.select(this.lineChart.row).remove();
- this.lineChart.destroy();
- }
- });
- }
- }
-
- function addFootnote$1() {
- var footnoteText = [
- 'The y-axis for each chart is set to the ' +
- this.edish.config.measureBounds
- .map(function (bound) {
- var percentile = '' + Math.round(bound * 100);
- var lastDigit = +percentile.substring(percentile.length - 1);
- var text =
- percentile +
- ([0, 4, 5, 6, 7, 8, 9].indexOf(lastDigit) > -1
- ? 'th'
- : lastDigit === 3
- ? 'rd'
- : lastDigit === 2
- ? 'nd'
- : 'st');
- return text;
- })
- .join(' and ') +
- " percentiles of the entire population's results for that measure. " +
- 'Values outside the normal range are plotted as individual points. ' +
- 'Click a sparkline to view a more detailed version of the chart.'
- ];
- var footnotes = this.wrap.selectAll('span.footnote').data(footnoteText, function (d) {
- return d;
- });
-
- footnotes
- .enter()
- .append('span')
- .attr('class', 'footnote')
- .style('font-size', '0.7em')
- .style('padding-top', '0.1em')
- .text(function (d) {
- return d;
- });
-
- footnotes.exit().remove();
- }
-
- function addExtraMeasureToggle() {
- var measureTable = this;
- var chart = this.edish;
- var config = chart.config;
-
- measureTable.wrap.selectAll('div.wc-controls').remove();
-
- //check to see if there are extra measures in the MeasureTable
- var specifiedMeasures = Object.keys(config.measure_values).map(function (e) {
- return config.measure_values[e];
- });
- var tableMeasures = measureTable.data.raw.map(function (f) {
- return f.key;
- });
-
- //if extra measure exist...
- if (tableMeasures.length > specifiedMeasures.length) {
- var extraRows = measureTable.table
- .select('tbody')
- .selectAll('tr')
- .filter(function (f) {
- return specifiedMeasures.indexOf(f.key) == -1;
- });
-
- //hide extra rows by default
- extraRows.style('display', 'none');
-
- //add a toggle
- var toggleDiv = measureTable.wrap
- .insert('div', '*')
- .attr('class', 'wc-controls')
- .append('div')
- .attr('class', 'control-group');
- var extraCount = tableMeasures.length - specifiedMeasures.length;
- toggleDiv
- .append('span')
- .attr('class', 'wc-control-label')
- .style('display', 'inline-block')
- .style('padding-right', '.3em')
- .text(
- 'Show ' +
- extraCount +
- ' additional measure' +
- (extraCount == 1 ? '' : 's') +
- ':'
- );
- var toggle = toggleDiv.append('input').property('type', 'checkbox');
- toggle.on('change', function () {
- var showRows = this.checked;
- extraRows.style('display', showRows ? null : 'none');
- });
- }
- }
-
- function drawMeasureTable(d) {
- var nested = makeNestedData.call(this, d);
-
- //draw the measure table
- this.measureTable.edish = this;
- this.measureTable.on('draw', function () {
- addSparkLines.call(this);
- addSparkClick.call(this);
- addExtraMeasureToggle.call(this);
- addFootnote$1.call(this);
- });
- this.measureTable.draw(nested);
- }
-
- function makeParticipantHeader(d) {
- var chart = this;
- var wrap = this.participantDetails.header;
- var raw = d.values.raw[0];
- var title = this.participantDetails.header
- .append('h3')
- .attr('class', 'id')
- .html('Participant Details')
- .style('border-top', '2px solid black')
- .style('border-bottom', '2px solid black')
- .style('padding', '.2em');
-
- if (chart.config.participantProfileURL) {
- title
- .append('a')
- .html('Full Participant Profile')
- .attr('href', chart.config.participantProfileURL)
- .style('font-size', '0.8em')
- .style('padding-left', '1em');
- }
-
- title
- .append('Button')
- .text('Clear')
- .style('margin-left', '1em')
- .style('float', 'right')
- .on('click', function () {
- clearParticipantDetails.call(chart);
- });
-
- //show detail variables in a ul
- var ul = this.participantDetails.header
- .append('ul')
- .style('list-style', 'none')
- .style('padding', '0');
-
- var lis = ul
- .selectAll('li')
- .data(chart.config.details)
- .enter()
- .append('li')
- .style('', 'block')
- .style('display', 'inline-block')
- .style('text-align', 'center')
- .style('padding', '0.5em');
-
- lis.append('div')
- .text(function (d) {
- return d.label;
- })
- .style('font-size', '0.8em');
-
- lis.append('div').text(function (d) {
- return raw[d.value_col];
- });
-
- //show overall rRatio
- var rratio_li = ul
- .append('li')
- .style('', 'block')
- .style('display', 'inline-block')
- .style('text-align', 'center')
- .style('padding', '0.5em');
-
- rratio_li
- .append('div')
- .html('R Ratio')
- .style('font-size', '0.8em');
-
- rratio_li.append('div').text(d3.format('0.2f')(raw.rRatio));
-
- //show PALT`
- if (raw.p_alt) {
- var palt_li = ul
- .append('li')
- .style('', 'block')
- .style('display', 'inline-block')
- .style('text-align', 'center')
- .style('padding', '0.5em');
-
- palt_li
- .append('div')
- .html('PALT')
- .style('font-size', '0.8em');
-
- palt_li
- .append('div')
- .text(raw.p_alt.text_value)
- .style('border-bottom', '1px dotted #999')
- .style('cursor', 'pointer')
- .on('click', function () {
- wrap.select('p.footnote')
- .attr('class', 'footnote')
- .html(raw.p_alt.note);
- });
- }
-
- //initialize empty footnote
- wrap.append('p')
- .attr('class', 'footnote')
- .style('font-size', '0.7em')
- .style('padding', '0.5em');
- }
-
- var defaultSettings$1 = {
- max_width: 600,
- x: {
- column: null,
- type: 'linear',
- label: 'Study Day'
- },
- y: defineProperty(
- {
- column: 'relative_uln',
- type: 'linear',
- label: null, // set in ../callbacks/onPreprocess
- domain: null,
- format: '.1f'
- },
- 'domain',
- [0, null]
- ),
- marks: [
- {
- type: 'line',
- per: []
- },
- {
- type: 'circle',
- radius: 4,
- per: []
- }
- ],
- margin: { top: 20, bottom: 70 }, // bottom margin provides space for exposure plot
- gridlines: 'xy',
- color_by: null,
- colors: ['#e41a1c', '#377eb8', '#4daf4a', '#984ea3', '#ff7f00', '#ffff33', '#a65628'],
- aspect: 2
- };
-
- var controlInputs$1 = [
- {
- type: 'subsetter',
- label: 'Select Labs',
- value_col: null,
- multiple: true
- },
- {
- type: 'dropdown',
- label: 'Y-axis Display Type',
- description: null,
- option: 'displayLabel',
- start: null,
- values: null,
- require: true
- }
- ];
-
- function onLayout$1() {
- var spaghetti = this;
- var eDish = this.edish;
-
- //customize the display control
- var displayControlWrap = spaghetti.controls.wrap
- .selectAll('div')
- .filter(function (controlInput) {
- return controlInput.label === 'Y-axis Display Type';
- });
-
- var displayControl = displayControlWrap.select('select');
-
- //set the start value
- var start_value = eDish.config.display_options.find(function (f) {
- return f.value == eDish.config.display;
- }).label;
-
- displayControl.selectAll('option').attr('selected', function (d) {
- return d == start_value ? 'selected' : null;
- });
-
- displayControl.on('change', function (d) {
- var currentLabel = this.value;
- var currentValue = eDish.config.display_options.find(function (f) {
- return f.label == currentLabel;
- }).value;
- spaghetti.config.y.column = currentValue;
- spaghetti.draw();
- });
- }
-
- function onPreprocess$1() {
- var config = this.config;
- var unit = this.config.y.column == 'relative_uln' ? '[xULN]' : '[xBaseline]';
- config.y.label = 'Standardized Result ' + unit;
- }
-
- function drawCutLine(d) {
- //bit of a hack to make this work with paths and circles
- var spaghetti = this;
- var config = this.config;
- var raw = d.values.raw ? d.values.raw[0] : d.values[0].values.raw[0];
- var cut = raw[config.y.column + '_cut'];
- var param = raw[config.color_by];
- spaghetti.cutLine = spaghetti.svg
- .append('line')
- .attr('y1', spaghetti.y(cut))
- .attr('y2', spaghetti.y(cut))
- .attr('x1', 0)
- .attr('x2', spaghetti.plot_width)
- .attr('stroke', spaghetti.colorScale(param))
- .attr('stroke-dasharray', '3 3');
- spaghetti.cutLabel = spaghetti.svg
- .append('text')
- .attr('y', spaghetti.y(cut))
- .attr('dy', '-0.2em')
- .attr('x', spaghetti.plot_width)
- .attr('text-anchor', 'end')
- .attr('alignment-baseline', 'baseline')
- .attr('fill', spaghetti.colorScale(param))
- .text(d3.format('0.1f')(cut));
- }
-
- function addPointTitles$1() {
- var spaghetti = this;
- var config = this.edish.config;
- var points = this.marks[1].circles;
- points.select('title').remove();
- points.append('title').text(function (d) {
- var raw = d.values.raw[0];
- var ylabel = spaghetti.config.displayLabel;
- var yvar = spaghetti.config.y.column;
- var studyday_label = 'Study day: ' + raw[config.studyday_col] + '\n',
- visitn_label = config.visitn_col
- ? 'Visit Number: ' + raw[config.visitn_col] + '\n'
- : '',
- visit_label = config.visit_col ? 'Visit: ' + raw[config.visit_col] + '\n' : '',
- raw_label =
- 'Raw ' +
- raw[config.measure_col] +
- ': ' +
- d3.format('0.3f')(raw[config.value_col]) +
- '\n',
- adj_label =
- 'Adjusted ' + raw[config.measure_col] + ': ' + d3.format('0.3f')(raw[yvar]);
- return studyday_label + visit_label + visitn_label + raw_label + adj_label;
- });
- }
-
- function addExposure() {
- var context = this;
- this.svg.select('.se-exposure-supergroup').remove();
-
- //If exposure data exists, annotate exposures beneath x-axis.
- if (this.edish.exposure.include) {
- var supergroup = this.svg
- .insert('g', '.supergroup')
- .classed('se-exposure-supergroup', true);
- var dy = 20; // offset from chart
- var strokeWidth = 5; // width/diameter of marks
- this.svg.selectAll('.x.axis .tick text').attr('dy', dy + strokeWidth * 3 + 'px'); // offset x-axis tick labels
-
- //top boundary line
- supergroup.append('line').attr({
- x1: -this.margin.left,
- y1: this.plot_height + dy - strokeWidth * 2,
- x2: this.plot_width,
- y2: this.plot_height + dy - strokeWidth * 2,
- stroke: 'black',
- 'stroke-opacity': 0.1
- });
-
- //Exposure text
- supergroup
- .append('text')
- .attr({
- x: -3,
- y: this.plot_height + dy + strokeWidth,
- 'text-anchor': 'end',
- textLength: this.margin.left - 3
- })
- .text('Exposure');
-
- //bottom boundary line
- supergroup.append('line').attr({
- x1: -this.margin.left,
- y1: this.plot_height + dy + strokeWidth * 2,
- x2: this.plot_width,
- y2: this.plot_height + dy + strokeWidth * 2,
- stroke: 'black',
- 'stroke-opacity': 0.1
- });
-
- //Exposures
- var groups = supergroup
- .selectAll('g.se-exposure-group')
- .data(this.exposure_data)
- .enter()
- .append('g')
- .classed('se-exposure-group', true);
- groups.each(function (d) {
- var group = d3.select(this);
-
- //draw a line if exposure start and end dates are unequal
- if (
- d[context.edish.config.exposure_stdy_col] !==
- d[context.edish.config.exposure_endy_col]
- ) {
- group
- .append('line')
- .classed('se-exposure-line', true)
- .attr({
- x1: function x1(d) {
- return context.x(+d[context.edish.config.exposure_stdy_col]);
- },
- y1: context.plot_height + dy,
- x2: function x2(d) {
- return context.x(+d[context.edish.config.exposure_endy_col]);
- },
- y2: context.plot_height + dy,
- stroke: 'black',
- 'stroke-width': strokeWidth,
- 'stroke-opacity': 0.25
- })
- .on('mouseover', function (d) {
- this.setAttribute('stroke-width', strokeWidth * 2);
-
- //annotate a rectangle in the chart
- group
- .append('rect')
- .classed('se-exposure-highlight', true)
- .attr({
- x: function x(d) {
- return context.x(
- +d[context.edish.config.exposure_stdy_col]
- );
- },
- y: 0,
- width: function width(d) {
- return (
- context.x(+d[context.edish.config.exposure_endy_col]) -
- context.x(+d[context.edish.config.exposure_stdy_col])
- );
- },
- height: context.plot_height,
- fill: 'black',
- 'fill-opacity': 0.25
- });
- })
- .on('mouseout', function (d) {
- this.setAttribute('stroke-width', strokeWidth);
-
- //remove rectangle from the chart
- group.select('.se-exposure-highlight').remove();
- })
- .append('title')
- .text(
- 'Study Day: ' +
- d[context.edish.config.exposure_stdy_col] +
- '-' +
- d[context.edish.config.exposure_endy_col] +
- ' (' +
- (+d[context.edish.config.exposure_endy_col] -
- +d[context.edish.config.exposure_stdy_col] +
- (+d[context.edish.config.exposure_endy_col] >=
- +d[context.edish.config.exposure_stdy_col])) +
- ' days)\nTreatment: ' +
- d[context.edish.config.exposure_trt_col] +
- '\nDose: ' +
- d[context.edish.config.exposure_dose_col] +
- ' ' +
- d[context.edish.config.exposure_dosu_col]
- );
- }
- //draw a circle if exposure start and end dates are equal
- else {
- group
- .append('circle')
- .classed('se-exposure-circle', true)
- .attr({
- cx: function cx(d) {
- return context.x(+d[context.edish.config.exposure_stdy_col]);
- },
- cy: context.plot_height + dy,
- r: strokeWidth / 2,
- fill: 'black',
- 'fill-opacity': 0.25,
- stroke: 'black',
- 'stroke-opacity': 1
- })
- .on('mouseover', function (d) {
- this.setAttribute('r', strokeWidth);
-
- //annotate a vertical line in the chart
- group
- .append('line')
- .classed('se-exposure-highlight', true)
- .attr({
- x1: context.x(+d[context.edish.config.exposure_stdy_col]),
- y1: 0,
- x2: context.x(+d[context.edish.config.exposure_stdy_col]),
- y2: context.plot_height,
- stroke: 'black',
- 'stroke-width': 1,
- 'stroke-opacity': 0.5,
- 'stroke-dasharray': '3 1'
- });
- })
- .on('mouseout', function (d) {
- this.setAttribute('r', strokeWidth / 2);
-
- //remove vertical line from the chart
- group.select('.se-exposure-highlight').remove();
- })
- .append('title')
- .text(
- 'Study Day: ' +
- d[context.edish.config.exposure_stdy_col] +
- '\nTreatment: ' +
- d[context.edish.config.exposure_trt_col] +
- '\nDose: ' +
- d[context.edish.config.exposure_dose_col] +
- ' ' +
- d[context.edish.config.exposure_dosu_col]
- );
- }
- });
- }
- }
-
- function onResize() {
- var spaghetti = this;
- var config = this.config;
-
- addPointTitles$1.call(this);
-
- //fill circles above the cut point
- var y_col = this.config.y.column;
- this.marks[1].circles
- .attr('fill-opacity', function (d) {
- return d.values.raw[0][y_col + '_flagged'] ? 1 : 0;
- })
- .attr('fill-opacity', function (d) {
- return d.values.raw[0][y_col + '_flagged'] ? 1 : 0;
- });
-
- //Show cut lines on mouseover
- this.marks[1].circles
- .on('mouseover', function (d) {
- drawCutLine.call(spaghetti, d);
- })
- .on('mouseout', function () {
- spaghetti.cutLine.remove();
- spaghetti.cutLabel.remove();
- });
-
- this.marks[0].paths
- .on('mouseover', function (d) {
- drawCutLine.call(spaghetti, d);
- })
- .on('mouseout', function () {
- spaghetti.cutLine.remove();
- spaghetti.cutLabel.remove();
- });
-
- //annotate treatment exposure
- addExposure.call(this);
-
- //embiggen clip-path so points aren't clipped
- var radius = this.config.marks.find(function (mark) {
- return mark.type === 'circle';
- }).radius;
- this.svg
- .select('.plotting-area')
- .attr('width', this.plot_width + radius * 2 + 2) // plot width + circle radius * 2 + circle stroke width * 2
- .attr('height', this.plot_height + radius * 2 + 2) // plot height + circle radius * 2 + circle stroke width * 2
- .attr(
- 'transform',
- 'translate(-' +
- (radius + 1) + // translate left circle radius + circle stroke width
- ',-' +
- (radius + 1) + // translate up circle radius + circle stroke width
- ')'
- );
- }
-
- function onDraw$1() {
- var _this = this;
-
- var spaghetti = this;
- var eDish = this.edish;
-
- //make sure x-domain includes the extent of the exposure data
- if (this.edish.exposure.include) {
- this.exposure_data = this.edish.exposure.data.filter(function (d) {
- return d[_this.edish.config.id_col] === _this.edish.participantsSelected[0];
- });
- var extent = [
- d3.min(this.exposure_data, function (d) {
- return +d[_this.edish.config.exposure_stdy_col];
- }),
- d3.max(this.exposure_data, function (d) {
- return +d[_this.edish.config.exposure_endy_col];
- })
- ];
- if (extent[0] < this.x_dom[0]) this.x_dom[0] = extent[0];
- if (extent[1] > this.x_dom[1]) this.x_dom[1] = extent[1];
- }
-
- //make sure y domain includes the current cut point for all measures
- var max_value = d3.max(spaghetti.filtered_data, function (f) {
- return f[spaghetti.config.y.column];
- });
- var max_cut = d3.max(spaghetti.filtered_data, function (f) {
- return f[spaghetti.config.y.column + '_cut'];
- });
- var y_max = d3.max([max_value, max_cut]);
- spaghetti.config.y.domain = [0, y_max];
- spaghetti.y_dom = spaghetti.config.y.domain;
-
- //initialize the measureTable
- if (spaghetti.config.firstDraw) {
- drawMeasureTable.call(eDish, this.participant_data);
- spaghetti.config.firstDraw = false;
- }
- }
-
- function init$3(d) {
- var chart = this; //the full eDish object
- var config = this.config; //the eDish config
- var matches = d.values.raw[0].raw.filter(function (f) {
- return f.key_measure;
- });
-
- if ('spaghetti' in chart) {
- chart.spaghetti.destroy();
- }
-
- //sync settings
- defaultSettings$1.x.column = config.studyday_col;
- defaultSettings$1.color_by = config.measure_col;
- defaultSettings$1.marks[0].per = [config.id_col, config.measure_col];
- defaultSettings$1.marks[1].per = [config.id_col, config.studyday_col, config.measure_col];
- defaultSettings$1.firstDraw = true; //only initailize the measure table on first draw
-
- //flag variables above the cut-off
- matches.forEach(function (d) {
- var measure = d[config['measure_col']];
- var label = Object.keys(config.measure_values).find(function (key) {
- return config.measure_values[key] == measure;
- });
-
- d.relative_uln_cut = config.cuts[label].relative_uln;
- d.relative_baseline_cut = config.cuts[label].relative_baseline;
-
- d.relative_uln_flagged = d.relative_uln >= d.relative_uln_cut;
- d.relative_baseline_flagged = d.relative_baseline >= d.relative_baseline_cut;
- });
-
- //update the controls
- var spaghettiElement = this.element + ' .participantDetails .spaghettiPlot .chart';
-
- //Add y axis type options
- controlInputs$1.find(function (f) {
- return f.label == 'Y-axis Display Type';
- }).values = config.display_options.map(function (m) {
- return m.label;
- });
-
- //sync parameter filter
- controlInputs$1.find(function (f) {
- return f.label == 'Select Labs';
- }).value_col = config.measure_col;
-
- var spaghettiControls = webcharts.createControls(spaghettiElement, {
- location: 'top',
- inputs: controlInputs$1
- });
-
- //draw that chart
- if (!this.exposure.include) delete defaultSettings$1.margin.bottom; // use default bottom margin when not plotting exposure
- chart.spaghetti = webcharts.createChart(
- spaghettiElement,
- defaultSettings$1,
- spaghettiControls
- );
-
- chart.spaghetti.edish = chart; //link the full eDish object
- chart.spaghetti.participant_data = d; //include the passed data (used to initialize the measure table)
- chart.spaghetti.on('layout', onLayout$1);
- chart.spaghetti.on('preprocess', onPreprocess$1);
- chart.spaghetti.on('draw', onDraw$1);
- chart.spaghetti.on('resize', onResize);
- chart.spaghetti.init(matches);
-
- //add informational footnote
- chart.spaghetti.wrap
- .append('div')
- .attr('class', 'footnote')
- .style('font-size', '0.7em')
- .style('padding-top', '0.1em')
- .text(
- 'Points are filled for values above the current reference value. Mouseover a line to see the reference line for that lab.'
- );
- }
-
- function drawCutLines() {
- var chart = this;
- var config = this.config;
-
- //line at R Ratio = 2
- chart.svg
- .append('line')
- .attr('y1', chart.y(2))
- .attr('y2', chart.y(2))
- .attr('x1', 0)
- .attr('x2', chart.plot_width)
- .attr('stroke', '#999')
- .attr('stroke-dasharray', '3 3');
-
- chart.svg
- .append('text')
- .attr('y', chart.y(2))
- .attr('dy', '-0.2em')
- .attr('x', chart.plot_width)
- .attr('text-anchor', 'end')
- .attr('alignment-baseline', 'baseline')
- .attr('fill', '#999')
- .text('2');
-
- //line at R Ratio = 5
- chart.svg
- .append('line')
- .attr('y1', chart.y(5))
- .attr('y2', chart.y(5))
- .attr('x1', 0)
- .attr('x2', chart.plot_width)
- .attr('stroke', '#999')
- .attr('stroke-dasharray', '3 3');
-
- chart.svg
- .append('text')
- .attr('y', chart.y(5))
- .attr('dy', '-0.2em')
- .attr('x', chart.plot_width)
- .attr('text-anchor', 'end')
- .attr('alignment-baseline', 'baseline')
- .attr('fill', '#999')
- .text('5');
- }
-
- function updateClipPath() {
- //embiggen clip-path so points aren't clipped
- var radius = this.config.marks.find(function (mark) {
- return mark.type === 'circle';
- }).radius;
- this.svg
- .select('.plotting-area')
- .attr('width', this.plot_width + radius * 2 + 2) // plot width + circle radius * 2 + circle stroke width * 2
- .attr('height', this.plot_height + radius * 2 + 2) // plot height + circle radius * 2 + circle stroke width * 2
- .attr(
- 'transform',
- 'translate(-' +
- (radius + 1) + // translate left circle radius + circle stroke width
- ',-' +
- (radius + 1) + // translate up circle radius + circle stroke width
- ')'
- );
- }
-
- function addPointTitles$2() {
- var config = this.edish.config;
- var points = this.marks[1].circles;
- points.select('title').remove();
- points.append('title').text(function (d) {
- var raw = d.values.raw[0];
-
- var studyday_label = 'Study day: ' + raw[config.studyday_col] + '\n',
- visitn_label = config.visitn_col
- ? 'Visit Number: ' + raw[config.visitn_col] + '\n'
- : '',
- visit_label = config.visit_col ? 'Visit: ' + raw[config.visit_col] + '\n' : '',
- rratio_label = 'R Ratio: ' + d3.format('0.2f')(raw.rRatio);
- return studyday_label + visit_label + visitn_label + rratio_label;
- });
- }
-
- function onResize$1() {
- drawCutLines.call(this);
- updateClipPath.call(this);
- addPointTitles$2.call(this);
- }
-
- function onDraw$2() {
- var chart = this;
- var config = this.config;
-
- //make sure y domain includes the current cut point for all measures
- var max_value = d3.max(chart.filtered_data, function (f) {
- return f[chart.config.y.column];
- });
- var max_cut = 5;
- var y_max = d3.max([max_value, max_cut]);
- chart.config.y.domain = [0, y_max];
- chart.y_dom = chart.config.y.domain;
- }
-
- var defaultSettings$2 = {
- max_width: 600,
- x: {
- column: 'day',
- type: 'linear',
- label: 'Study Day'
- },
- y: {
- column: 'rRatio',
- type: 'linear',
- label: 'R Ratio [(ALT/ULN) / (ALP/ULN)]',
- format: '.1f',
- domain: [0, null]
- },
- marks: [
- {
- type: 'line',
- per: ['id']
- },
- {
- type: 'circle',
- radius: 4,
- per: ['id', 'day']
- }
- ],
- margin: { top: 20 },
- gridlines: 'xy',
- aspect: 2
- };
-
- function init$4(d) {
- var chart = this; //the full eDish object
- var config = this.config; //the eDish config
- var matches = d.values.raw[0].rRatio_raw;
-
- if ('rRatioChart' in chart) {
- chart.rRatioChart.destroy();
- }
-
- //sync settings
- defaultSettings$2.marks[0].per = [config.id_col];
- defaultSettings$2.marks[1].per = [config.id_col, config.studyday_col];
-
- //draw that chart
- var rrElement = this.element + ' .participantDetails .rrPlot .chart';
- chart.rRatioChart = webcharts.createChart(rrElement, defaultSettings$2);
-
- chart.rRatioChart.edish = chart; //link the full eDish object
-
- chart.rRatioChart.on('draw', onDraw$2);
- chart.rRatioChart.on('resize', onResize$1);
-
- chart.rRatioChart.init(matches);
- }
-
- function addPointClick() {
- var chart = this;
- var config = this.config;
- var points = this.marks[0].circles;
-
- //add event listener to all participant level points
- points.on('click', function (d) {
- //Stop animation.
- chart.svg.transition().duration(0);
- chart.controls.studyDayPlayButton.datum({ state: 'play' });
- chart.controls.studyDayPlayButton.html('►');
-
- // Reset the details view
- clearParticipantDetails.call(chart, d); //clear the previous participant
- chart.config.quadrants.table.wrap.style('display', 'none'); //hide the quadrant summary
-
- //Update chart object & trigger the participantsSelected event on the overall chart.
- chart.participantsSelected = [d.key];
- chart.events.participantsSelected.data = chart.participantsSelected;
- chart.wrap.node().dispatchEvent(chart.events.participantsSelected);
-
- //format the eDish chart
- points
- .attr('stroke', '#ccc') //set all points to gray
- .attr('fill', 'white')
- .classed('disabled', true); //disable mouseover while viewing participant details
-
- d3.select(this)
- .attr('stroke', function (d) {
- return chart.colorScale(d.values.raw[0][config.color_by]);
- }) //highlight selected point
- .attr('stroke-width', 3);
-
- //Add elements to the eDish chart
- drawVisitPath.call(chart, d); //draw the path showing participant's pattern over time
- drawRugs.call(chart, d, 'x');
- drawRugs.call(chart, d, 'y');
-
- //draw the "detail view" for the clicked participant
- chart.participantDetails.wrap.selectAll('*').style('display', null);
- makeParticipantHeader.call(chart, d);
- init$3.call(chart, d); //NOTE: the measure table is initialized from within the spaghettiPlot
- init$4.call(chart, d);
- });
- }
-
- function addPointTitles$3() {
- var config = this.config;
- var points = this.marks[0].circles;
- points.select('title').remove();
- points.append('title').text(function (d) {
- var xvar = config.x.column;
- var yvar = config.y.column;
- var raw = d.values.raw[0],
- xLabel =
- config.x.label +
- ': ' +
- d3.format('0.2f')(raw[xvar]) +
- ' @ Day ' +
- raw[xvar + '_' + config.studyday_col],
- yLabel =
- config.y.label +
- ': ' +
- d3.format('0.2f')(raw[yvar]) +
- ' @ Day ' +
- raw[yvar + '_' + config.studyday_col],
- dayDiff = raw['day_diff'] + ' days apart',
- idLabel = 'Participant ID: ' + raw[config.id_col],
- rRatioLabel = config.r_ratio_filter
- ? '\n' + 'R Ratio: ' + d3.format('0.2f')(raw.rRatio)
- : '';
- return idLabel + rRatioLabel + '\n' + xLabel + '\n' + yLabel + '\n' + dayDiff;
- });
- }
-
- function setPointSize() {
- var _this = this;
-
- var chart = this;
- var config = this.config;
- var points = this.marks[0].circles;
-
- //create the scale
- var base_size = config.marks[0].radius || config.flex_point_size;
- var max_size = base_size * 5;
- var small_size = base_size / 2;
-
- if (config.point_size != 'Uniform') {
- var sizeValues_all = d3.merge(
- chart.raw_data.map(function (m) {
- return m[config.point_size + '_raw'];
- })
- );
- var sizeDomain_all = d3.extent(sizeValues_all, function (f) {
- return f.value;
- });
- var sizeDomain_max = d3.extent(
- chart.raw_data.map(function (m) {
- return m[config.point_size];
- })
- );
- var sizeDomain_rRatio = [
- 0,
- d3.max(this.raw_data, function (d) {
- return d.rRatio_max;
- })
- ];
- var sizeDomain =
- config.point_size == 'rRatio'
- ? sizeDomain_rRatio
- : config.plot_max_values
- ? sizeDomain_max
- : sizeDomain_all;
- chart.sizeScale = d3.scale
- .linear()
- .range([base_size, max_size])
- .domain(sizeDomain);
- }
-
- //TODO: draw a legend (coming later?)
-
- //set the point radius
- points
- .transition()
- .attr('r', function (d) {
- // console.log(config.point_size);
- var raw = d.values.raw[0];
- // console.log(raw);
- if (raw.outOfRange) {
- return small_size;
- } else if (config.point_size == 'Uniform') {
- return base_size;
- } else {
- return chart.sizeScale(raw[config.point_size]);
- }
- })
- .attr('cx', function (d) {
- return _this.x(d.values.x);
- })
- .attr('cy', function (d) {
- return _this.y(d.values.y);
- });
- }
-
- function setPointOpacity() {
- var config = this.config;
- var points = this.marks[0].circles;
-
- points.attr('fill-opacity', function (d) {
- var raw = d.values.raw[0];
- if (config.plot_max_values) {
- // if viewing max values, fill based on time window
- return raw.day_diff <= config.visit_window ? 0.5 : 0;
- } else {
- //fill points after participant's first day
- return config.plot_day < raw.day_range[0] ? 0 : 0.5;
- }
- });
- }
-
- // Reposition any exisiting participant marks when the chart is resized
- function updateParticipantMarks() {
- var chart = this;
- var config = this.config;
-
- //reposition participant visit path
- var myNewLine = d3.svg
- .line()
- .x(function (d) {
- return chart.x(d.x);
- })
- .y(function (d) {
- return chart.y(d.y);
- });
-
- chart.visitPath
- .select('path')
- .transition()
- .attr('d', myNewLine);
-
- //reposition participant visit circles and labels
- chart.visitPath
- .selectAll('g.visit-point')
- .select('circle')
- .transition()
- .attr('cx', function (d) {
- return chart.x(d.x);
- })
- .attr('cy', function (d) {
- return chart.y(d.y);
- });
-
- chart.visitPath
- .selectAll('g.visit-point')
- .select('text.participant-visits')
- .transition()
- .attr('x', function (d) {
- return chart.x(d.x);
- })
- .attr('y', function (d) {
- return chart.y(d.y);
- });
-
- //reposition axis rugs
- chart.x_rug
- .selectAll('text')
- .transition()
- .attr('x', function (d) {
- return chart.x(d[config.display]);
- })
- .attr('y', function (d) {
- return chart.y(chart.y.domain()[0]);
- });
-
- chart.y_rug
- .selectAll('text')
- .transition()
- .attr('x', function (d) {
- return chart.x(chart.x.domain()[0]);
- })
- .attr('y', function (d) {
- return chart.y(d[config.display]);
- });
- }
-
- function customizePoints() {
- addPointMouseover.call(this);
- addPointClick.call(this);
- addPointTitles$3.call(this);
- formatPoints.call(this);
- setPointSize.call(this);
- setPointOpacity.call(this);
- updateParticipantMarks.call(this);
- }
-
- function drawQuadrants() {
- var _this = this;
-
- var config = this.config;
- var x_var = this.config.x.column;
- var y_var = this.config.y.column;
-
- var x_cut = this.config.cuts[x_var][config.display];
- var y_cut = this.config.cuts[y_var][config.display];
-
- //position for cut-point lines
- this.cut_lines.lines
- .filter(function (d) {
- return d.dimension == 'x';
- })
- .attr('x1', this.x(x_cut))
- .attr('x2', this.x(x_cut))
- .attr('y1', this.plot_height)
- .attr('y2', 0);
-
- this.cut_lines.lines
- .filter(function (d) {
- return d.dimension == 'y';
- })
- .attr('x1', 0)
- .attr('x2', this.plot_width)
- .attr('y1', function (d) {
- return _this.y(y_cut);
- })
- .attr('y2', function (d) {
- return _this.y(y_cut);
- });
-
- this.cut_lines.backing
- .filter(function (d) {
- return d.dimension == 'x';
- })
- .attr('x1', this.x(x_cut))
- .attr('x2', this.x(x_cut))
- .attr('y1', this.plot_height)
- .attr('y2', 0);
-
- this.cut_lines.backing
- .filter(function (d) {
- return d.dimension == 'y';
- })
- .attr('x1', 0)
- .attr('x2', this.plot_width)
- .attr('y1', function (d) {
- return _this.y(y_cut);
- })
- .attr('y2', function (d) {
- return _this.y(y_cut);
- });
-
- //position labels
- this.quadrant_labels.g.attr('display', null); //show labels if they're hidden
- this.quadrant_labels.g
- .select('text.upper-right')
- .attr('x', this.plot_width)
- .attr('y', 0);
-
- this.quadrant_labels.g
- .select('text.upper-left')
- .attr('x', 0)
- .attr('y', 0);
-
- this.quadrant_labels.g
- .select('text.lower-right')
- .attr('x', this.plot_width)
- .attr('y', this.plot_height);
-
- this.quadrant_labels.g
- .select('text.lower-left')
- .attr('x', 0)
- .attr('y', this.plot_height);
-
- this.quadrant_labels.text.text(function (d) {
- return d.label + ' (' + d.percent + ')';
- });
- }
-
- function addAxisLabelTitles() {
- var chart = this;
- var config = this.config;
-
- var details =
- config.display == 'relative_uln'
- ? 'Values are plotted as multiples of the upper limit of normal for the measure.'
- : config.display == 'relative_baseline'
- ? "Values are plotted as multiples of the partipant's baseline value for the measure."
- : config.display == 'absolute'
- ? ' Values are plotted using the raw units for the measure.'
- : null;
-
- var axisLabels = chart.svg
- .selectAll('.axis')
- .select('.axis-title')
- .select('tspan')
- .remove();
-
- var axisLabels = chart.svg
- .selectAll('.axis')
- .select('.axis-title')
- .append('tspan')
- .html(function (d) {
- //var current = d3.select(this).text();
- return ' ⓘ';
- })
- .attr('font-size', '0.8em')
- .style('cursor', 'help')
- .append('title')
- .text(details);
- }
-
- function toggleLegend() {
- var hideLegend = this.config.color_by == 'NONE';
- this.wrap.select('.legend').style('display', hideLegend ? 'None' : 'block');
- }
-
- function dragStarted() {
- var dimension = d3.select(this).classed('x') ? 'x' : 'y';
- var chart = d3.select(this).datum().chart;
-
- d3.select(this)
- .select('line.cut-line')
- .attr('stroke-width', '2')
- .attr('stroke-dasharray', '2,2');
-
- chart.quadrant_labels.g.style('display', 'none');
- }
-
- function dragged() {
- var chart = d3.select(this).datum().chart;
-
- var x = d3.event.dx;
- var y = d3.event.dy;
-
- var line = d3.select(this).select('line.cut-line');
- var lineBack = d3.select(this).select('line.cut-line-backing');
-
- var dimension = d3.select(this).classed('x') ? 'x' : 'y';
-
- // Update the line properties
- var attributes = {
- x1: Math.max(0, parseInt(line.attr('x1')) + (dimension == 'x' ? x : 0)),
- x2: Math.max(0, parseInt(line.attr('x2')) + (dimension == 'x' ? x : 0)),
- y1: Math.min(chart.plot_height, parseInt(line.attr('y1')) + (dimension == 'y' ? y : 0)),
- y2: Math.min(chart.plot_height, parseInt(line.attr('y2')) + (dimension == 'y' ? y : 0))
- };
-
- line.attr(attributes);
- lineBack.attr(attributes);
-
- var rawCut = line.attr(dimension + '1');
- var current_cut = +d3.format('0.1f')(chart[dimension].invert(rawCut));
-
- //update the cut control in real time
- chart.controls.wrap
- .selectAll('div.control-group')
- .filter(function (f) {
- return f.description
- ? f.description.toLowerCase() == dimension + '-axis reference line'
- : false;
- })
- .select('input')
- .node().value = current_cut;
- var measure = chart.config[dimension].column;
- chart.config.cuts[measure][chart.config.display] = current_cut;
- }
-
- function dragEnded() {
- var chart = d3.select(this).datum().chart;
-
- d3.select(this)
- .select('line.cut-line')
- .attr('stroke-width', '1')
- .attr('stroke-dasharray', '5,5');
- chart.quadrant_labels.g.style('display', null);
-
- //redraw the chart (updates the needed cutpoint settings and quadrant annotations)
- chart.draw();
- }
-
- // credit to https://bl.ocks.org/dimitardanailov/99950eee511375b97de749b597147d19
-
- function init$5() {
- var drag = d3.behavior
- .drag()
- .origin(function (d) {
- return d;
- })
- .on('dragstart', dragStarted)
- .on('drag', dragged)
- .on('dragend', dragEnded);
-
- this.cut_lines.wrap.moveToFront();
- this.cut_lines.g.call(drag);
- }
-
- function addBoxPlot(
- svg,
- results,
- height,
- width,
- domain,
- boxPlotWidth,
- boxColor,
- boxInsideColor,
- fmt,
- horizontal,
- log
- ) {
- //set default orientation to "horizontal"
- var horizontal = horizontal == undefined ? true : horizontal;
-
- //make the results numeric and sort
- var results = results
- .map(function (d) {
- return +d;
- })
- .sort(d3.ascending);
-
- //set up d3.scales
- if (horizontal) {
- var y = log ? d3.scale.log() : d3.scale.linear();
- y.range([height, 0]).domain(domain);
- var x = d3.scale.linear().range([0, width]);
- } else {
- var x = log ? d3.scale.log() : d3.scale.linear();
- x.range([0, width]).domain(domain);
- var y = d3.scale.linear().range([height, 0]);
- }
-
- var probs = [0.05, 0.25, 0.5, 0.75, 0.95];
- for (var i = 0; i < probs.length; i++) {
- probs[i] = d3.quantile(results, probs[i]);
- }
-
- var boxplot = svg
- .append('g')
- .attr('class', 'boxplot')
- .datum({ values: results, probs: probs });
-
- //draw rectangle from q1 to q3
- var box_x = horizontal ? x(0.5 - boxPlotWidth / 2) : x(probs[1]);
- var box_width = horizontal
- ? x(0.5 + boxPlotWidth / 2) - x(0.5 - boxPlotWidth / 2)
- : x(probs[3]) - x(probs[1]);
- var box_y = horizontal ? y(probs[3]) : y(0.5 + boxPlotWidth / 2);
- var box_height = horizontal
- ? -y(probs[3]) + y(probs[1])
- : y(0.5 - boxPlotWidth / 2) - y(0.5 + boxPlotWidth / 2);
-
- boxplot
- .append('rect')
- .attr('class', 'boxplot fill')
- .attr('x', box_x)
- .attr('width', box_width)
- .attr('y', box_y)
- .attr('height', box_height)
- .style('fill', boxColor);
-
- //draw dividing lines at d3.median, 95% and 5%
- var iS = [0, 2, 4];
- var iSclass = ['', 'd3.median', ''];
- var iSColor = [boxColor, boxInsideColor, boxColor];
- for (var i = 0; i < iS.length; i++) {
- boxplot
- .append('line')
- .attr('class', 'boxplot ' + iSclass[i])
- .attr('x1', horizontal ? x(0.5 - boxPlotWidth / 2) : x(probs[iS[i]]))
- .attr('x2', horizontal ? x(0.5 + boxPlotWidth / 2) : x(probs[iS[i]]))
- .attr('y1', horizontal ? y(probs[iS[i]]) : y(0.5 - boxPlotWidth / 2))
- .attr('y2', horizontal ? y(probs[iS[i]]) : y(0.5 + boxPlotWidth / 2))
- .style('fill', iSColor[i])
- .style('stroke', iSColor[i]);
- }
-
- //draw lines from 5% to 25% and from 75% to 95%
- var iS = [[0, 1], [3, 4]];
- for (var i = 0; i < iS.length; i++) {
- boxplot
- .append('line')
- .attr('class', 'boxplot')
- .attr('x1', horizontal ? x(0.5) : x(probs[iS[i][0]]))
- .attr('x2', horizontal ? x(0.5) : x(probs[iS[i][1]]))
- .attr('y1', horizontal ? y(probs[iS[i][0]]) : y(0.5))
- .attr('y2', horizontal ? y(probs[iS[i][1]]) : y(0.5))
- .style('stroke', boxColor);
- }
-
- boxplot
- .append('circle')
- .attr('class', 'boxplot d3.mean')
- .attr('cx', horizontal ? x(0.5) : x(d3.mean(results)))
- .attr('cy', horizontal ? y(d3.mean(results)) : y(0.5))
- .attr('r', horizontal ? x(boxPlotWidth / 3) : y(1 - boxPlotWidth / 3))
- .style('fill', boxInsideColor)
- .style('stroke', boxColor);
-
- boxplot
- .append('circle')
- .attr('class', 'boxplot d3.mean')
- .attr('cx', horizontal ? x(0.5) : x(d3.mean(results)))
- .attr('cy', horizontal ? y(d3.mean(results)) : y(0.5))
- .attr('r', horizontal ? x(boxPlotWidth / 6) : y(1 - boxPlotWidth / 6))
- .style('fill', boxColor)
- .style('stroke', 'None');
-
- var formatx = fmt ? d3.format(fmt) : d3.format('.2f');
-
- boxplot
- .selectAll('.boxplot')
- .append('title')
- .text(function (d) {
- return (
- 'N = ' +
- d.values.length +
- '\n' +
- 'Min = ' +
- d3.min(d.values) +
- '\n' +
- '5th % = ' +
- formatx(d3.quantile(d.values, 0.05)) +
- '\n' +
- 'Q1 = ' +
- formatx(d3.quantile(d.values, 0.25)) +
- '\n' +
- 'Median = ' +
- formatx(d3.median(d.values)) +
- '\n' +
- 'Q3 = ' +
- formatx(d3.quantile(d.values, 0.75)) +
- '\n' +
- '95th % = ' +
- formatx(d3.quantile(d.values, 0.95)) +
- '\n' +
- 'Max = ' +
- d3.max(d.values) +
- '\n' +
- 'Mean = ' +
- formatx(d3.mean(d.values)) +
- '\n' +
- 'StDev = ' +
- formatx(d3.deviation(d.values))
- );
- });
- }
-
- function init$6() {
- // Draw box plots
- this.svg.selectAll('g.yMargin').remove();
- this.svg.selectAll('g.xMargin').remove();
-
- // Y-axis box plot
- var yValues = this.current_data.map(function (d) {
- return d.values.y;
- });
- var ybox = this.svg.append('g').attr('class', 'yMargin');
- addBoxPlot(
- ybox,
- yValues,
- this.plot_height,
- 1,
- this.y_dom,
- 10,
- '#bbb',
- 'white',
- '0.2f',
- true,
- this.config.y.type == 'log'
- );
- ybox.select('g.boxplot').attr(
- 'transform',
- 'translate(' + (this.plot_width + this.config.margin.right / 2) + ',0)'
- );
-
- //X-axis box plot
- var xValues = this.current_data.map(function (d) {
- return d.values.x;
- });
- var xbox = this.svg.append('g').attr('class', 'xMargin');
- addBoxPlot(
- xbox, //svg element
- xValues, //values
- 1, //height
- this.plot_width, //width
- this.x_dom, //domain
- 10, //box plot width
- '#bbb', //box color
- 'white', //detail color
- '0.2f', //format
- false, // horizontal?
- this.config.y.type == 'log' // log?
- );
- xbox.select('g.boxplot').attr(
- 'transform',
- 'translate(0,' + -(this.config.margin.top / 2) + ')'
- );
- }
-
- function adjustTicks() {
- this.svg
- .selectAll('.x.axis .tick text')
- .attr({
- transform: 'rotate(-45)',
- dx: -10,
- dy: 10
- })
- .style('text-anchor', 'end');
- }
-
- function updateTimingFootnote() {
- var config = this.config;
- if (config.plot_max_values) {
- var windowText =
- config.visit_window == 0
- ? 'on the same day'
- : config.visit_window == 1
- ? 'within 1 day'
- : 'within ' + config.visit_window + ' days';
- var timingFootnote =
- ' Points where maximum ' +
- config.measure_values[config.x.column] +
- ' and ' +
- config.measure_values[config.y.column] +
- ' values were collected ' +
- windowText +
- ' are filled, others are empty.';
-
- this.footnote.timing.text(timingFootnote);
- } else {
- var timingFootnote =
- "Small points are drawn when the selected day occurs outside of the participant's study enrollment; either the first collected measures (empty circle) or last collected measures (filled circle) for the participant are plotted for these points.";
- this.footnote.timing.text(timingFootnote);
- }
- }
-
- function onResize$2() {
- //add maximum point interactivity, custom title and formatting
- customizePoints.call(this);
-
- //draw the quadrants and add drag interactivity
- updateSummaryTable.call(this);
- drawQuadrants.call(this);
- init$5.call(this);
-
- // hide the legend if no group options are given
- toggleLegend.call(this);
-
- // add boxplots
- init$6.call(this);
-
- //axis formatting
- adjustTicks.call(this);
- addAxisLabelTitles.call(this);
-
- //add timing footnote
- updateTimingFootnote.call(this);
- }
-
- function onDestroy() {
- this.emptyChartWarning.remove();
- }
-
- var callbacks = {
- onInit: onInit,
- onLayout: onLayout,
- onPreprocess: onPreprocess,
- onDataTransform: onDataTransform,
- onDraw: onDraw,
- onResize: onResize$2,
- onDestroy: onDestroy
- };
-
- function init$7() {
- var lb = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
- var ex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
-
- //const data = mergeData(lb,ex);
- this.data = {
- lb: lb,
- ex: ex
- };
- this.chart.exposure = {
- include: Array.isArray(ex) && ex.length,
- data: ex
- };
- this.chart.init(lb);
- }
-
- function destroy() {
- this.chart.destroy();
- }
-
- function hepexplorer(element, settings) {
- var initial_settings = clone(settings);
- var defaultSettings = configuration.settings();
- var controlInputs = configuration.controlInputs();
- var mergedSettings = Object.assign({}, defaultSettings, settings);
- var syncedSettings = configuration.syncSettings(mergedSettings);
- var syncedControlInputs = configuration.syncControlInputs(controlInputs, syncedSettings);
- var controls = webcharts.createControls(element, {
- location: 'top',
- inputs: syncedControlInputs
- });
- var chart = webcharts.createChart(element, syncedSettings, controls);
-
- chart.element = element;
- chart.initial_settings = initial_settings;
-
- //Define callbacks.
- for (var callback in callbacks) {
- chart.on(callback.substring(2).toLowerCase(), callbacks[callback]);
- }
- var hepexplorer = {
- element: element,
- settings: settings,
- chart: chart,
- init: init$7,
- destroy: destroy
- };
-
- return hepexplorer;
- }
-
- return hepexplorer;
-});
diff --git a/inst/htmlwidgets/lib/paneled-outlier-explorer-1.1.4/paneledOutlierExplorer.js b/inst/htmlwidgets/lib/paneled-outlier-explorer-1.1.4/paneledOutlierExplorer.js
deleted file mode 100644
index 1899da22..00000000
--- a/inst/htmlwidgets/lib/paneled-outlier-explorer-1.1.4/paneledOutlierExplorer.js
+++ /dev/null
@@ -1,2101 +0,0 @@
-(function(global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined'
- ? (module.exports = factory(require('d3'), require('webcharts')))
- : typeof define === 'function' && define.amd
- ? define(['d3', 'webcharts'], factory)
- : (global.paneledOutlierExplorer = factory(global.d3, global.webCharts));
-})(this, function(d3, webcharts) {
- 'use strict';
-
- if (typeof Object.assign != 'function') {
- (function() {
- Object.assign = function(target) {
- if (target === undefined || target === null) {
- throw new TypeError('Cannot convert undefined or null to object');
- }
-
- var output = Object(target);
- for (var index = 1; index < arguments.length; index++) {
- var source = arguments[index];
- if (source !== undefined && source !== null) {
- for (var nextKey in source) {
- if (source.hasOwnProperty(nextKey)) {
- output[nextKey] = source[nextKey];
- }
- }
- }
- }
- return output;
- };
- })();
- }
-
- if (!Array.prototype.find) {
- Object.defineProperty(Array.prototype, 'find', {
- value: function value(predicate) {
- // 1. Let O be ? ToObject(this value).
- if (this == null) {
- throw new TypeError('"this" is null or not defined');
- }
-
- var o = Object(this);
-
- // 2. Let len be ? ToLength(? Get(O, "length")).
- var len = o.length >>> 0;
-
- // 3. If IsCallable(predicate) is false, throw a TypeError exception.
- if (typeof predicate !== 'function') {
- throw new TypeError('predicate must be a function');
- }
-
- // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
- var thisArg = arguments[1];
-
- // 5. Let k be 0.
- var k = 0;
-
- // 6. Repeat, while k < len
- while (k < len) {
- // a. Let Pk be ! ToString(k).
- // b. Let kValue be ? Get(O, Pk).
- // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)).
- // d. If testResult is true, return kValue.
- var kValue = o[k];
- if (predicate.call(thisArg, kValue, k, o)) {
- return kValue;
- }
- // e. Increase k by 1.
- k++;
- }
-
- // 7. Return undefined.
- return undefined;
- }
- });
- }
-
- (function() {
- if (typeof window.CustomEvent === 'function') return false;
-
- function CustomEvent(event, params) {
- params = params || { bubbles: false, cancelable: false, detail: null };
- var evt = document.createEvent('CustomEvent');
- evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
- return evt;
- }
-
- window.CustomEvent = CustomEvent;
- })();
-
- function defineStyles() {
- var styles = [
- /***--------------------------------------------------------------------------------------\
- Controls
- \--------------------------------------------------------------------------------------***/
-
- '#paneled-outlier-explorer #controls-header {' +
- ' margin: 0;' +
- ' overflow: hidden;' +
- ' background-color: #333;' +
- ' width: 24%;' +
- ' float: left;' +
- ' font-size: 150%;' +
- ' display: block;' +
- ' color: white;' +
- ' padding: 14px 16px;' +
- ' box-sizing: border-box;' +
- '}',
- '#paneled-outlier-explorer #left-side {' +
- ' width: 24%;' +
- ' float: left;' +
- '}',
- '#paneled-outlier-explorer #left-side > * {' +
- ' width: 100%;' +
- ' display: inline-block;' +
- '}',
- '#paneled-outlier-explorer #left-side .wc-controls {' +
- ' padding: 10px 0;' +
- '}',
- '#paneled-outlier-explorer #left-side .wc-controls .control-group {' +
- ' float: left;' +
- ' clear: left;' +
- ' margin: 0 0 2px 0;' +
- ' border: 1px solid white;' +
- '}',
- '#paneled-outlier-explorer #left-side .wc-controls .control-group.inlier-highlighting {' +
- ' background-color: rgba(0,255,0,.05);' +
- ' border: 1px solid green;' +
- '}',
- '#paneled-outlier-explorer #left-side .wc-controls .control-group > * {' +
- ' display: inline-block;' +
- ' margin-left: 3px;' +
- '}',
- '#paneled-outlier-explorer #left-side #measure-list-container {' +
- ' padding:0' +
- '}',
- '#paneled-outlier-explorer #left-side #measure-list-container #measure-list-header {' +
- ' font-size: 150%;' +
- ' border-top: 1px solid lightgray;' +
- ' font-weight: lighter;' +
- ' padding: 14px 0;' +
- '}',
- '#paneled-outlier-explorer #left-side #measure-list-container #measure-list-checkbox {' +
- ' margin: 5px;' +
- '}',
- '#paneled-outlier-explorer #left-side #measure-list-container #measure-list {' +
- ' list-style-type: none;' +
- ' font-weight: lighter;' +
- '}',
- '#paneled-outlier-explorer #left-side #measure-list-container .measure-item {' +
- '}',
- '#paneled-outlier-explorer #left-side #measure-list-container .measure-item-container {' +
- '}',
- '#paneled-outlier-explorer #left-side #measure-list-container .measure-checkbox {' +
- ' margin: 5px;' +
- '}',
-
- /***--------------------------------------------------------------------------------------\
- Navigation
- \--------------------------------------------------------------------------------------***/
-
- '#paneled-outlier-explorer ul#navigation-bar {' +
- ' list-style-type: none;' +
- ' margin: 0;' +
- ' padding: 0;' +
- ' overflow: hidden;' +
- ' background-color: #333;' +
- ' width: 75%;' +
- ' float: right;' +
- '}',
- '#paneled-outlier-explorer ul#navigation-bar li {' +
- ' display: block;' +
- ' color: white;' +
- ' text-align: center;' +
- '}',
- '#paneled-outlier-explorer ul#navigation-bar li.navigation {' +
- ' float: left;' +
- ' cursor: pointer;' +
- ' font-size: 24px;' +
- ' padding: 14px 16px;' +
- ' text-decoration: none;' +
- '}',
- '#paneled-outlier-explorer ul#navigation-bar li#population-annotation {' +
- ' float: right;' +
- ' font-size: 16px;' +
- ' padding: 18px 16px;' +
- '}',
- '#paneled-outlier-explorer ul#navigation-bar li.navigation.active {' +
- ' background-color: #111;' +
- '}',
- '#paneled-outlier-explorer ul#navigation-bar li.navigation:hover {' +
- ' background-color: #111;' +
- '}',
- '#paneled-outlier-explorer ul#navigation-bar li.navigation#Listing-nav.brushed {' +
- ' color: orange;' +
- '}',
-
- /***--------------------------------------------------------------------------------------\
- Charts
- \--------------------------------------------------------------------------------------***/
-
- '#paneled-outlier-explorer div.wc-layout.wc-small-multiples#Charts {' +
- ' width: 75%;' +
- ' float: right;' +
- ' padding-top: 10px;' +
- '}',
- '#paneled-outlier-explorer div.wc-layout.wc-small-multiples#Charts > div.wc-chart {' +
- ' padding: 0 1em 0 0;' +
- '}',
- '#paneled-outlier-explorer div.wc-layout.wc-small-multiples#Charts > div.wc-chart.expanded {' +
- ' width: 100%;' +
- ' }',
- '#paneled-outlier-explorer div.wc-layout.wc-small-multiples#Charts > div.wc-chart .wc-chart-title {' +
- ' text-align: left;' +
- ' font-size: .9em;' +
- '}',
- '#paneled-outlier-explorer div.wc-layout.wc-small-multiples#Charts > div.wc-chart .chart-button {' +
- ' float: right;' +
- ' cursor: pointer;' +
- ' border: 1px solid black;' +
- ' border-radius: 3px;' +
- ' padding: 0px 3px 1px 3px;' +
- ' font-size: 75%;' +
- ' margin-left: 5px;' +
- ' visibility: hidden;' +
- '}',
- '#paneled-outlier-explorer div.wc-layout.wc-small-multiples#Charts > div.wc-chart .chart-button:hover {' +
- ' background: black;' +
- ' color: white;' +
- '}',
- '#paneled-outlier-explorer div.wc-layout.wc-small-multiples#Charts > div.wc-chart text.no-data {' +
- ' fill: red;' +
- ' font-size: 0.8em;' +
- '}',
- '#paneled-outlier-explorer .normal-range {' +
- ' fill: green;' +
- ' fill-opacity: .05;' +
- ' stroke: green;' +
- ' stroke-opacity: 1;' +
- '}',
- '#paneled-outlier-explorer .n-inlier {' + ' cursor: help;' + '}',
- '#paneled-outlier-explorer .n-inlier text {' +
- ' fill: green;' +
- ' text-anchor: end;' +
- ' font-size: 10px;' +
- ' font-weight: bold;' +
- '}',
- '#paneled-outlier-explorer .n-inlier rect {' +
- ' fill: green;' +
- ' fill-opacity: .05;' +
- ' stroke: green;' +
- ' stroke-opacity: 1;' +
- '}',
- '#paneled-outlier-explorer .single-point {' +
- ' stroke-width: 3;' +
- ' stroke-opacity: 1;' +
- '}',
-
- /***--------------------------------------------------------------------------------------\
- Listing
- \--------------------------------------------------------------------------------------***/
-
- '#paneled-outlier-explorer div.wc-chart#Listing {' +
- ' width: 75%;' +
- ' float: right;' +
- ' padding-top: 10px;' +
- ' overflow-x: scroll;' +
- '}',
- '#paneled-outlier-explorer div.wc-chart#Listing table {' +
- ' width: 100%;' +
- ' display: table;' +
- '}',
-
- /***--------------------------------------------------------------------------------------\
- General styles
- \--------------------------------------------------------------------------------------***/
-
- '#paneled-outlier-explorer .hidden {' + ' display: none !important;' + '}',
- '#paneled-outlier-explorer path.brushed {' +
- ' stroke: orange;' +
- ' stroke-width: 3px;' +
- ' stroke-opacity: 1;' +
- '}',
- '#paneled-outlier-explorer tr.brushed {' +
- ' border: 2px solid orange !important;' +
- '}'
- ],
- style = document.createElement('style');
- style.type = 'text/css';
- style.innerHTML = styles.join('\n');
-
- document.getElementsByTagName('head')[0].appendChild(style);
- }
-
- var _typeof =
- typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol'
- ? function(obj) {
- return typeof obj;
- }
- : function(obj) {
- return obj &&
- typeof Symbol === 'function' &&
- obj.constructor === Symbol &&
- obj !== Symbol.prototype
- ? 'symbol'
- : typeof obj;
- };
-
- function clone(obj) {
- var copy = void 0;
-
- //boolean, number, string, null, undefined
- if ('object' != (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) || null == obj)
- return obj;
-
- //date
- if (obj instanceof Date) {
- copy = new Date();
- copy.setTime(obj.getTime());
- return copy;
- }
-
- //array
- if (obj instanceof Array) {
- copy = [];
- for (var i = 0, len = obj.length; i < len; i++) {
- copy[i] = clone(obj[i]);
- }
- return copy;
- }
-
- //object
- if (obj instanceof Object) {
- copy = {};
- for (var attr in obj) {
- if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
- }
- return copy;
- }
-
- throw new Error('Unable to copy [obj]! Its type is not supported.');
- }
-
- var rendererSettings = {
- measure_col: 'TEST',
- time_cols: [
- {
- value_col: 'VISIT',
- type: 'ordinal',
- order: null,
- order_col: 'VISITNUM',
- label: 'Visit',
- rotate_tick_labels: true,
- vertical_space: 75
- },
- {
- value_col: 'DY',
- type: 'linear',
- order: null,
- order_col: 'DY',
- label: 'Study Day',
- rotate_tick_labels: false,
- vertical_space: 0
- }
- ],
- value_col: 'STRESN',
- id_col: 'USUBJID',
- unit_col: 'STRESU',
- lln_col: 'STNRLO',
- uln_col: 'STNRHI',
- measures: null,
- filters: null,
- multiples_sizing: {
- width: 350,
- height: 175
- },
- inliers: false,
- normal_range_method: 'LLN-ULN',
- normal_range_sd: 1.96,
- normal_range_quantile_low: 0.05,
- normal_range_quantile_high: 0.95,
- visits_without_data: false,
- unscheduled_visits: false,
- unscheduled_visit_pattern: '/unscheduled|early termination/i',
- unscheduled_visit_values: null // takes precedence over unscheduled_visit_pattern visits_without_data: false,
- };
-
- var webchartsSettings = {
- x: {
- type: null, // sync to [ time_cols[0].type ]
- column: null, // sync to [ time_cols[0].value_col ]
- label: '' // sync to [ time_cols[0].label ]
- },
- y: {
- type: 'linear',
- column: null, // sync to [ value_col ]
- label: ''
- },
- marks: [
- {
- type: 'line',
- per: null, // sync to [ id_col ] and [ measure_col ]
- attributes: {
- 'stroke-width': 1,
- 'stroke-opacity': 0.2,
- stroke: 'black'
- }
- }
- ],
- resizable: false,
- scale_text: false,
- margin: {
- bottom: 0,
- left: 50
- },
- gridlines: 'xy'
- };
-
- var defaultSettings = Object.assign(rendererSettings, webchartsSettings);
-
- function syncSettings(settings) {
- var syncedSettings = clone(settings);
- syncedSettings.x.type = settings.time_cols[0].type;
- syncedSettings.x.order = settings.time_cols[0].order;
- syncedSettings.x.column = settings.time_cols[0].value_col;
- syncedSettings.x.rotate_tick_labels = settings.time_cols[0].rotate_tick_labels;
- syncedSettings.y.column = settings.value_col;
- syncedSettings.marks[0].per = [settings.id_col, settings.measure_col];
- syncedSettings.width = syncedSettings.multiples_sizing.width;
- syncedSettings.height = syncedSettings.multiples_sizing.height;
-
- //handle a string arguments to array settings
- var array_settings = ['filters'];
- array_settings.forEach(function(s) {
- if (!(syncedSettings[s] instanceof Array))
- syncedSettings[s] =
- typeof syncedSettings[s] === 'string' ? [syncedSettings[s]] : [];
- });
-
- //Convert unscheduled_visit_pattern from string to regular expression.
- if (
- typeof syncedSettings.unscheduled_visit_pattern === 'string' &&
- syncedSettings.unscheduled_visit_pattern !== ''
- ) {
- var flags = settings.unscheduled_visit_pattern.replace(/.*?\/([gimy]*)$/, '$1'),
- pattern = settings.unscheduled_visit_pattern.replace(
- new RegExp('^/(.*?)/' + flags + '$'),
- '$1'
- );
- syncedSettings.unscheduled_visit_regex = new RegExp(pattern, flags);
- }
-
- return syncedSettings;
- }
-
- var controlInputs = [
- {
- type: 'dropdown',
- label: 'X-axis',
- option: 'x.column',
- require: true
- },
- {
- type: 'checkbox',
- label: 'Visits without data',
- option: 'visits_without_data'
- },
- {
- type: 'checkbox',
- label: 'Unscheduled visits',
- option: 'unscheduled_visits'
- },
- {
- type: 'checkbox',
- label: 'Normal range inliers',
- option: 'inliers'
- },
- {
- type: 'dropdown',
- label: 'Normal range method',
- option: 'normal_range_method',
- values: ['None', 'LLN-ULN', 'Standard Deviation', 'Quantiles'],
- require: true
- },
- {
- type: 'number',
- label: 'Number of standard deviations',
- option: 'normal_range_sd'
- },
- {
- type: 'number',
- label: 'Lower quantile',
- option: 'normal_range_quantile_low'
- },
- {
- type: 'number',
- label: 'Upper quantile',
- option: 'normal_range_quantile_high'
- }
- ];
-
- function syncControlInputs(controlInputs, settings) {
- var syncedControlInputs = clone(controlInputs);
-
- syncedControlInputs.filter(function(controlInput) {
- return controlInput.label === 'X-axis';
- })[0].values = settings.time_cols.map(function(d) {
- return d.value_col || d;
- });
-
- if (settings.filters.length > 0)
- settings.filters.forEach(function(filter) {
- syncedControlInputs.push({
- type: 'subsetter',
- value_col: filter.value_col || filter,
- label: filter.label || filter.value_col || filter,
- description: 'filter',
- multiple: false
- });
- });
-
- //Remove unscheduled visit control if unscheduled visit pattern is unscpecified.
- if (!(settings.unscheduled_visit_regex || settings.unscheduled_visit_values))
- controlInputs.splice(
- controlInputs
- .map(function(controlInput) {
- return controlInput.label;
- })
- .indexOf('Unscheduled visits'),
- 1
- );
-
- return syncedControlInputs;
- }
-
- function removeVariables() {
- var _this = this;
-
- //Define set of required variables.
- this.config.variables = d3
- .set(
- d3.merge([
- [this.config.measure_col],
- [this.config.id_col],
- this.config.time_cols.map(function(time_col) {
- return time_col.value_col;
- }),
- this.config.time_cols.map(function(time_col) {
- return time_col.order_col;
- }),
- [this.config.value_col],
- [this.config.unit_col],
- [this.config.lln_col],
- [this.config.uln_col],
- this.config.filters
- ? this.config.filters.map(function(filter) {
- return filter.value_col ? filter.value_col : filter;
- })
- : []
- ])
- )
- .values()
- .filter(function(variable) {
- return Object.keys(_this.data.initial[0]).indexOf(variable) > -1;
- });
-
- //Delete extraneous variables.
- this.data.initial.forEach(function(d) {
- for (var variable in d) {
- if (_this.config.variables.indexOf(variable) < 0) delete d[variable];
- }
- });
-
- //If data do not have normal range variables update normal range method setting and options.
- if (
- this.config.variables.indexOf(this.config.lln_col) < 0 ||
- this.config.variables.indexOf(this.config.uln_col) < 0
- ) {
- if (this.config.normal_range_method === 'LLN-ULN')
- this.config.normal_range_method = 'Standard Deviation';
- this.controls.config.inputs
- .find(function(input) {
- return input.option === 'normal_range_method';
- })
- .values.splice(1, 1);
- }
- }
-
- function deriveVariables() {
- var _this = this;
-
- var ordinalTimeSettings = this.config.time_cols.find(function(time_col) {
- return time_col.type === 'ordinal';
- });
-
- this.data.raw.forEach(function(d) {
- //Concatenate measure and unit.
- if (d[_this.config.unit_col])
- d.measure_unit =
- d[_this.config.measure_col] + ' (' + d[_this.config.unit_col] + ')';
- else d.measure_unit = d[_this.config.measure_col];
-
- //Identify unscheduled visits.
- d.unscheduled = false;
- if (ordinalTimeSettings) {
- if (_this.config.unscheduled_visit_values)
- d.unscheduled =
- _this.config.unscheduled_visit_values.indexOf(
- d[ordinalTimeSettings.value_col]
- ) > -1;
- else if (_this.config.unscheduled_visit_regex)
- d.unscheduled = _this.config.unscheduled_visit_regex.test(
- d[ordinalTimeSettings.value_col]
- );
- }
- });
- }
-
- function defineData(data) {
- var _this = this;
-
- this.data = {
- initial: data
- };
-
- //Remove extraneous variables.
- removeVariables.call(this);
-
- //Define arrays of all IDs, filtered IDs, and selected IDs.
- this.data.IDs = {
- raw: d3
- .set(
- this.data.initial.map(function(d) {
- return d[_this.config.id_col];
- })
- )
- .values()
- };
-
- //Remove invalid data.
- this.data.raw = this.data.initial.filter(function(d) {
- return (
- !/^\s*$/.test(d[_this.config.measure_col]) &&
- /^[0-9.]+$/.test(d[_this.config.value_col])
- );
- });
- this.data.filtered = this.data.raw;
- this.data.IDs.filtered = d3
- .set(
- this.data.filtered.map(function(d) {
- return d[_this.config.id_col];
- })
- )
- .values();
- this.data.IDs.selected = [];
-
- //Derive additional variables.
- deriveVariables.call(this);
-
- //Warn user of dropped records.
- if (this.data.raw.length !== this.data.initial.length)
- console.warn(
- this.data.initial.length -
- this.data.raw.length +
- ' non-numeric observations have been removed from the data.'
- );
- }
-
- function captureMeasures() {
- var _this = this;
-
- this.config.allMeasures = d3
- .set(
- this.data.raw.map(function(d) {
- return d.measure_unit;
- })
- )
- .values()
- .sort(function(a, b) {
- var leftSort = a < b,
- rightSort = a > b;
-
- if (_this.config.measures && _this.config.measures.length) {
- var aPos = _this.config.measures.indexOf(a),
- bPos = _this.config.measures.indexOf(b),
- diff = aPos > -1 && bPos > -1 ? aPos - bPos : null;
-
- return diff
- ? diff
- : aPos > -1
- ? -1
- : bPos > -1
- ? 1
- : leftSort
- ? -1
- : rightSort
- ? 1
- : 0;
- } else return leftSort ? -1 : rightSort ? 1 : 0;
- });
- this.config.measures =
- this.config.measures && this.config.measures.length
- ? this.config.measures
- : this.config.allMeasures;
- }
-
- function defineVisitOrder() {
- var _this = this;
-
- this.config.time_cols.forEach(function(time_settings) {
- if (time_settings.type === 'ordinal') {
- var visits = void 0,
- visitOrder = void 0;
-
- //Given an ordering variable sort a unique set of visits by the ordering variable.
- if (
- time_settings.order_col &&
- _this.data.raw[0].hasOwnProperty(time_settings.order_col)
- ) {
- //Define a unique set of visits with visit order concatenated.
- visits = d3
- .set(
- _this.data.raw.map(function(d) {
- return (
- d[time_settings.order_col] + '|' + d[time_settings.value_col]
- );
- })
- )
- .values();
-
- //Sort visits.
- visitOrder = visits
- .sort(function(a, b) {
- var aOrder = a.split('|')[0],
- bOrder = b.split('|')[0],
- diff = +aOrder - +bOrder;
- return diff ? diff : d3.ascending(a, b);
- })
- .map(function(visit) {
- return visit.split('|')[1];
- });
- } else {
- //Otherwise sort a unique set of visits alphanumerically.
- //Define a unique set of visits.
- visits = d3
- .set(
- _this.data.raw.map(function(d) {
- return d[time_settings.value_col];
- })
- )
- .values();
-
- //Sort visits;
- visitOrder = visits.sort();
- }
-
- //Set x-axis domain.
- if (time_settings.order) {
- //If a visit order is specified, use it and concatenate any unspecified visits at the end.
- time_settings.order = time_settings.order.concat(
- visitOrder.filter(function(visit) {
- return time_settings.order.indexOf(visit) < 0;
- })
- );
- }
- //Otherwise use data-driven visit order.
- else time_settings.order = visitOrder;
-
- //Define domain.
- time_settings.domain = time_settings.order;
- } else if (time_settings.type === 'linear') {
- time_settings.order = null;
- time_settings.domain = d3.extent(_this.data.raw, function(d) {
- return +d[time_settings.value_col];
- });
- }
- });
- }
-
- function toggleCharts(chart) {
- var toggle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
-
- var measureListCheckbox = d3.select('#measure-list-checkbox'),
- checked = measureListCheckbox.property('checked'),
- measureItems = d3.selectAll('li.measure-item'),
- anyUnchecked = measureItems[0].some(function(measureItem) {
- return !measureItem.getElementsByTagName('input')[0].checked;
- });
-
- //Handle overall toggle.
- if (toggle) {
- measureListCheckbox.attr('title', checked ? 'Remove all charts' : 'Display all charts');
- measureItems.each(function(d) {
- d3.select(this)
- .select('input')
- .property('checked', checked);
- toggleChart(chart, this, d);
- });
- measureListCheckbox.property('checked', checked);
- } else {
- //Handle individual toggles.
- measureListCheckbox.attr(
- 'title',
- anyUnchecked ? 'Display all charts' : 'Remove all charts'
- );
- measureListCheckbox.property('checked', !anyUnchecked);
- }
- }
-
- function toggleChart(chart, li) {
- //Determine state of checkbox.
- var checkbox = d3.select(li).select('input'),
- checked = checkbox.property('checked');
- checkbox.attr('title', checked ? 'Remove chart' : 'Display chart');
- d3.select(chart.div)
- .selectAll('.wc-chart')
- .filter(function(di) {
- return di.measure === d3.select(li).datum();
- })
- .classed('hidden', !checked);
-
- //If any checkbox is unchecked, uncheck measureListCheckbox.
- toggleCharts(chart, false);
- }
-
- function layout() {
- this.wrap.attr('id', 'Charts');
- this.listing.wrap.attr('id', 'Listing').classed('hidden', true);
-
- var chart = this;
-
- //Navigation bar.
- var navigationBar = this.container
- .insert('ul', ':first-child')
- .attr('id', 'navigation-bar');
- var navigationButtons = navigationBar
- .selectAll('li.navigation')
- .data(['Charts', 'Listing'])
- .enter()
- .append('li')
- .classed('navigation', true)
- .classed('active', function(d) {
- return d === 'Charts';
- })
- .attr('id', function(d) {
- return d + '-nav';
- })
- .text(function(d) {
- return d;
- })
- .on('click', function(d) {
- navigationButtons
- .filter(function(di) {
- return di === d;
- })
- .classed('active', true);
- navigationButtons
- .filter(function(di) {
- return di !== d;
- })
- .classed('active', false);
- if (d === 'Charts') {
- d3.select('#Listing').classed('hidden', true);
- d3.select('#Charts').classed('hidden', false);
- } else {
- d3.select('#Charts').classed('hidden', true);
- d3.select('#Listing').classed('hidden', false);
- }
- });
-
- //Population annotation
- this.populationAnnotation = navigationBar
- .append('li')
- .attr('id', 'population-annotation')
- .html(
- ' of ' +
- ' participant(s) shown (' +
- ')'
- );
-
- //Create controls header.
- var controlsTab = this.container
- .insert('div', ':first-child')
- .attr('id', 'controls-header')
- .text('Controls');
-
- //Define all-chart toggle.
- var measureListContainer = this.container
- .select('#left-side')
- .append('ul')
- .attr('id', 'measure-list-container');
- var measureListHeader = measureListContainer
- .append('div')
- .attr('id', 'measure-list-header');
- var measureListCheckbox = measureListHeader
- .append('input')
- .attr({
- id: 'measure-list-checkbox',
- type: 'checkbox',
- title:
- this.config.measures.length === this.config.allMeasures.length
- ? 'Remove all charts'
- : 'Display all charts'
- })
- .property('checked', this.config.measures.length === this.config.allMeasures.length)
- .on('click', function() {
- toggleCharts(chart, this);
- });
- measureListHeader.append('span').text('Measures');
- var measureList = measureListContainer //Define individual chart toggles.
- .append('ul')
- .attr('id', 'measure-list');
- var measureItems = measureList
- .selectAll('li.measure-item')
- .data(this.config.allMeasures)
- .enter()
- .append('li')
- .attr('class', function(d) {
- return 'measure-item ' + d.replace(/[^a-z0-9-]/gi, '-');
- })
- .each(function(d) {
- //Append div inside list item.
- var measureItemContainer = d3
- .select(this)
- .append('div')
- .classed('measure-item-container', true);
- //Check whether measure should by displayed initially.
- var checked = chart.config.measures.indexOf(d) > -1; //Append checkbox inside div.
- var measureItemCheckbox = measureItemContainer
- .append('input')
- .classed('measure-checkbox', true)
- .attr({
- type: 'checkbox',
- title: checked ? 'Remove chart' : 'Display chart'
- })
- .property('checked', checked);
- var measureItemLabel = measureItemContainer.append('span').text(function(d) {
- return d;
- });
- })
- .on('change', function(d) {
- toggleChart(chart, this);
- });
- }
-
- function updatePopulationAnnotation() {
- this.populationAnnotation.select('#n-participants').text(this.data.IDs.filtered.length);
- this.populationAnnotation.select('#N-participants').text(this.data.IDs.raw.length);
- this.populationAnnotation
- .select('#n-N-rate')
- .text(d3.format('%')(this.data.IDs.filtered.length / this.data.IDs.raw.length));
- }
-
- function applyFilters(d) {
- var _this = this;
-
- this.data.IDs.selected = [];
-
- //Reset brush.
- this.multiples.forEach(function(multiple) {
- multiple.package.overlay.call(multiple.package.brush.clear());
- multiple.config.extent = multiple.package.brush.extent();
- });
-
- //De-highlight brushed lines.
- this.wrap.selectAll('.line-supergroup g.line path').classed('brushed', false);
-
- //De-highlight listing.
- d3.select('#Listing-nav').classed('brushed', false);
-
- //Define filtered data.
- if (d.type === 'subsetter') {
- this.data.filtered = this.data.raw;
- this.controls.config.inputs
- .filter(function(input) {
- return (
- input.type === 'subsetter' &&
- input.value !== 'All' &&
- input.value !== undefined
- );
- })
- .forEach(function(input) {
- _this.data.filtered = _this.data.filtered.filter(function(d) {
- return input.value === d[input.value_col];
- });
- });
- this.data.IDs.filtered = d3
- .set(
- this.data.filtered.map(function(d) {
- return d[_this.config.id_col];
- })
- )
- .values();
- updatePopulationAnnotation.call(this);
- }
- }
-
- function customizeControls() {
- var _this = this;
-
- var context = this,
- controls = this.controls.wrap
- .selectAll('.control-group')
- .classed('hidden', function(d) {
- return (
- (_this.config.normal_range_method !== 'Standard Deviation' &&
- /standard deviation/i.test(d.label)) ||
- (_this.config.normal_range_method !== 'Quantiles' &&
- /quantile/i.test(d.label))
- );
- });
-
- //Define x-axis option labels.
- controls
- .filter(function(control) {
- return control.label === 'X-axis';
- })
- .selectAll('option')
- .property('label', function(d) {
- return _this.config.time_cols
- .filter(function(time_col) {
- return time_col.value_col === d;
- })
- .pop().label;
- });
-
- //Define x-axis option labels.
- controls
- .filter(function(control) {
- return control.label === 'X-axis';
- })
- .selectAll('option')
- .property('label', function(d) {
- return _this.config.time_cols
- .filter(function(time_col) {
- return time_col.value_col === d;
- })
- .pop().label;
- });
-
- //Add custom x-domain and filter functionality.
- controls
- .filter(function(d) {
- return d.type === 'subsetter' || d.label === 'X-axis';
- })
- .on('change', function(d) {
- d.value = d3
- .select(this)
- .selectAll('option')
- .filter(function() {
- return this.selected;
- })
- .text();
- applyFilters.call(context, d);
- });
-
- //Add custom normal range functionality.
- var normalRangeControl = controls.filter(function(d) {
- return d.label === 'Normal range method';
- });
- normalRangeControl.on('change', function(d) {
- var normal_range_method = d3
- .select(this)
- .select('option:checked')
- .text();
-
- controls.classed('hidden', function(d) {
- return (
- (normal_range_method !== 'Standard Deviation' &&
- /standard deviation/i.test(d.label)) ||
- (normal_range_method !== 'Quantiles' && /quantile/i.test(d.label))
- );
- });
- });
- }
-
- function initCustomEvents() {
- var chart = this;
- chart.participantsSelected = [];
- chart.events.participantsSelected = new CustomEvent('participantsSelected');
- }
-
- function init(data) {
- //Attach various data arrays to charts.
- defineData.call(this, data);
-
- //Capture unique set of measures in data.
- captureMeasures.call(this);
-
- //Capture ordered set of visits.
- defineVisitOrder.call(this);
-
- //Define layout of renderer.
- layout.call(this);
-
- //Update population annotation with initial statistics.
- updatePopulationAnnotation.call(this);
-
- //Initialize charts.
- webcharts.multiply(this, this.data.raw, 'measure_unit', this.config.allMeasures);
-
- //Initialize listing.
- this.listing.config.cols = Object.keys(data[0]).filter(function(key) {
- return ['measure_unit', 'unscheduled', 'outlier'].indexOf(key) === -1;
- }); // remove system variables from listing
- this.listing.init(this.data.raw);
-
- //Define custom event listener for filters.
- customizeControls.call(this);
-
- //initialize custom events
- initCustomEvents.call(this);
- }
-
- function defineData$1() {
- var _this = this;
-
- this.data = {
- measure: this.filters[0].val
- };
- this.data.raw = this.raw_data.filter(function(d) {
- return d.measure_unit === _this.data.measure;
- });
- this.data.results = this.data.raw
- .map(function(d) {
- return +d[_this.config.value_col];
- })
- .sort(function(a, b) {
- return a - b;
- });
- this.data.yDomain = d3.extent(this.data.results);
- this.data.yRange = this.data.yDomain[1] - this.data.yDomain[0];
- this.data.yFormat =
- this.data.yRange < 0.1
- ? '.3f'
- : this.data.yRange < 1
- ? '.2f'
- : this.data.yRange < 10
- ? '.1f'
- : '1d';
- this.data.IDs = {
- raw: d3
- .set(
- this.data.raw.map(function(d) {
- return d[_this.config.id_col];
- })
- )
- .values()
- .sort()
- };
- }
-
- function onInit() {
- defineData$1.call(this);
- }
-
- function minimize(chart) {
- delete chart.parent.expandedChart;
- //Modify chart config and redraw.
- chart.wrap
- .select('.m__imize-chart')
- .html('+')
- .attr('title', 'Maximize chart');
- chart.wrap.classed('expanded', false);
-
- chart.config.previous_plot_width = chart.plot_width;
- chart.config.width = chart.config.initialSettings.width;
- chart.config.max_width = null;
- chart.config.height = chart.config.initialSettings.height;
- chart.config.aspect = null;
-
- chart.draw();
- }
-
- function m__imize(chart) {
- chart.config.previous_plot_width = chart.plot_width;
-
- //Maximize chart.
- if (!chart.wrap.classed('expanded')) {
- //Clear previously expanded chart.
- if (chart.parent.expandedChart) minimize(chart.parent.expandedChart);
-
- //Attach expanded chart to parent.
- chart.parent.expandedChart = chart;
-
- //Modify chart configuation and redraw.
- chart.wrap
- .select('.m__imize-chart')
- .html('−')
- .attr('title', 'Minimize chart');
- chart.wrap.classed('expanded', true);
-
- chart.config.width = null;
- chart.config.max_width = 9999;
- chart.config.height = null;
- chart.config.aspect = 2.5;
-
- chart.draw();
-
- //Sort expanded chart first.
- chart.parent.wrap.selectAll('.wc-chart').sort(function(a, b) {
- return a.measure === chart.data.measure
- ? -1
- : b.measure === chart.data.measure
- ? 1
- : chart.config.measures.indexOf(a.measure) -
- chart.config.measures.indexOf(b.measure);
- });
-
- //Scroll window to expanded chart.
- var bodyRect = document.body.getBoundingClientRect(),
- elemRect = chart.wrap.node().getBoundingClientRect(),
- offset = elemRect.top - bodyRect.top;
- window.scrollTo(0, offset);
- } else {
- //Minimize chart
- minimize(chart);
-
- //Revert to default sort.
- chart.parent.wrap.selectAll('.wc-chart').sort(function(a, b) {
- return (
- chart.config.measures.indexOf(a.measure) -
- chart.config.measures.indexOf(b.measure)
- );
- });
- }
- }
-
- function removeChart() {
- var _this = this;
-
- this.wrap
- .on('mouseover', function() {
- _this.wrap.selectAll('.wc-chart-title span').style('visibility', 'visible');
- })
- .on('mouseout', function() {
- _this.wrap.selectAll('.wc-chart-title span').style('visibility', 'hidden');
- })
- .select('.wc-chart-title')
- .append('span')
- .classed('remove-chart chart-button', true)
- .html('✖')
- .attr('title', 'Remove chart')
- .style('visibility', 'hidden')
- .on('click', function() {
- //Minimize chart.
- if (_this.wrap.classed('full-screen')) m__imize(_this);
-
- var li = d3.select(
- 'li.measure-item.' + _this.data.measure.replace(/[^a-z0-9-]/gi, '-')
- );
- li.select('input').property('checked', false);
- toggleChart(_this, li.node());
- });
- }
-
- function m__imizeChart() {
- var _this = this;
-
- var m__imizeButton = this.wrap
- .select('.wc-chart-title')
- .append('span')
- .classed('m__imize-chart chart-button', true)
- .html('+')
- .attr('title', 'Maximize chart');
- m__imizeButton.on('click', function() {
- m__imize(_this);
- });
- }
-
- function classChart() {
- this.wrap
- .classed(this.data.measure.replace(/[^a-z0-9-]/gi, '-'), true)
- .classed('hidden', this.config.measures.indexOf(this.data.measure) === -1);
- }
-
- function addInlierAnnotation() {
- this.inliersAnnotation = {
- g: this.svg.append('g').classed('n-inlier', true)
- };
- this.inliersAnnotation.text = this.inliersAnnotation.g.append('text');
- this.inliersAnnotation.rect = this.inliersAnnotation.g.append('rect');
- this.inliersAnnotation.title = this.inliersAnnotation.g.append('title');
- }
-
- function onLayout() {
- //Add button to the chart title that removes chart.
- removeChart.call(this);
-
- //Add button to the chart title that maximizes/minimizes chart.
- m__imizeChart.call(this);
-
- //Add measure-specific chart class and class that hides chart as needed.
- classChart.call(this);
-
- //Add node to svg for inliers annotation.
- addInlierAnnotation.call(this);
- }
-
- function removeVisitsWithoutData() {
- var _this = this;
-
- if (!this.config.visits_without_data) {
- this.config.x.domain = this.config.x.domain.filter(function(visit) {
- return (
- d3
- .set(
- _this.data.raw.map(function(d) {
- return d[_this.config.x.column];
- })
- )
- .values()
- .indexOf(visit) > -1
- );
- });
- }
- }
-
- function removeUnscheduledVisits() {
- var _this = this;
-
- if (!this.config.unscheduled_visits) {
- if (this.config.unscheduled_visit_values)
- this.config.x.domain = this.config.x.domain.filter(function(visit) {
- return _this.config.unscheduled_visit_values.indexOf(visit) < 0;
- });
- else if (this.config.unscheduled_visit_regex)
- this.config.x.domain = this.config.x.domain.filter(function(visit) {
- return !_this.config.unscheduled_visit_regex.test(visit);
- });
- }
- }
-
- function defineXsettings() {
- var _this = this;
-
- //Update x-object.
- Object.assign(
- this.config.x,
- this.config.time_cols.find(function(time_col) {
- return time_col.value_col === _this.config.x.column;
- })
- );
- this.config.x.label = '';
-
- //Remove visits without data from x-domain if x-type is ordinal.
- if (this.config.x.type === 'ordinal') {
- this.config.x.domain = this.config.x.order;
- removeVisitsWithoutData.call(this);
- removeUnscheduledVisits.call(this);
- }
-
- //Delete domain setting if x-type is linear
- if (this.config.x.type !== 'ordinal') delete this.config.x.domain;
-
- //Update bottom margin.
- this.config.margin.bottom = this.config.x.vertical_space;
- }
-
- function defineYsettings() {
- this.config.y.domain = this.data.domain;
- this.config.y.format = this.data.format;
- }
-
- function deriveStatistics() {
- var _this = this;
-
- if (this.config.normal_range_method === 'LLN-ULN') {
- this.lln = function(d) {
- return d instanceof Object
- ? +d[_this.config.lln_col]
- : d3.median(_this.data.raw, function(d) {
- return +d[_this.config.lln_col];
- });
- };
- this.uln = function(d) {
- return d instanceof Object
- ? +d[_this.config.uln_col]
- : d3.median(_this.data.raw, function(d) {
- return +d[_this.config.uln_col];
- });
- };
- } else if (this.config.normal_range_method === 'Standard Deviation') {
- this.mean = d3.mean(this.data.results);
- this.sd = d3.deviation(this.data.results);
- this.lln = function() {
- return _this.mean - _this.config.normal_range_sd * _this.sd;
- };
- this.uln = function() {
- return _this.mean + _this.config.normal_range_sd * _this.sd;
- };
- } else if (this.config.normal_range_method === 'Quantiles') {
- this.lln = function() {
- return d3.quantile(_this.data.results, _this.config.normal_range_quantile_low);
- };
- this.uln = function() {
- return d3.quantile(_this.data.results, _this.config.normal_range_quantile_high);
- };
- } else {
- this.lln = function(d) {
- return d instanceof Object ? d[_this.config.value_col] + 1 : _this.data.results[0];
- };
- this.uln = function(d) {
- return d instanceof Object
- ? d[_this.config.value_col] - 1
- : _this.data.results[_this.data.results.length - 1];
- };
- }
- }
-
- function deriveVariables$1() {
- var _this = this;
-
- this.data.raw.forEach(function(d) {
- d.outlier =
- d[_this.config.value_col] < _this.lln(d) ||
- d[_this.config.value_col] > _this.uln(d);
- });
- this.data.IDs.outliers = d3
- .set(
- this.data.raw
- .filter(function(d) {
- return d.outlier;
- })
- .map(function(d) {
- return d[_this.config.id_col];
- })
- )
- .values();
- }
-
- function defineFilteredData() {
- var _this = this;
-
- this.data.filtered = this.data.raw;
-
- //Apply current filters.
- this.filters
- .filter(function(filter) {
- return filter.col !== 'measure_unit' && filter.val !== 'All';
- })
- .forEach(function(filter) {
- _this.data.filtered = _this.data.filtered.filter(function(d) {
- return Array.isArray(filter.val)
- ? filter.val.indexOf(d[filter.col]) > -1
- : filter.val === d[filter.col];
- });
- });
-
- //Capture IDs with data matching the current filters.
- this.data.IDs.filtered = d3
- .set(
- this.data.filtered.map(function(d) {
- return d[_this.config.id_col];
- })
- )
- .values()
- .sort();
- }
-
- function defineDisplayedData() {
- var _this = this;
-
- this.data.displayed = this.data.filtered;
-
- //Remove unscheduled visits.
- if (!this.config.unscheduled_visits)
- this.data.displayed = this.data.displayed.filter(function(d) {
- return !d.unscheduled;
- });
-
- //Remove inliers.
- if (!this.config.inliers)
- this.data.displayed = this.data.displayed.filter(function(d) {
- return (
- _this.data.IDs.outliers.indexOf(d[_this.config.id_col]) > -1 ||
- _this.parent.data.IDs.selected.indexOf(d[_this.config.id_col]) > -1
- );
- });
-
- //Capture IDs that will actually be displayed.
- this.data.IDs.displayed = d3
- .set(
- this.data.displayed.map(function(d) {
- return d[_this.config.id_col];
- })
- )
- .values()
- .sort();
- }
-
- function onPreprocess() {
- defineXsettings.call(this);
- defineYsettings.call(this);
- deriveStatistics.call(this);
- deriveVariables$1.call(this);
- defineFilteredData.call(this);
- defineDisplayedData.call(this);
- this.raw_data = this.data.displayed;
- }
-
- function onDatatransform() {}
-
- function onDraw() {
- if (this.package) this.package.overlay.call(this.package.brush.clear());
- }
-
- function resetChart() {
- this.svg.selectAll('*').style('visibility', 'visible');
- this.svg.select('text.no-data').remove();
- this.svg.select('.normal-range').remove();
- }
-
- function definePackage() {
- //Capture each multiple's scale.
- this.package = {
- measure: this.data.measure,
- container: this.wrap,
- overlay: this.svg.append('g').classed('brush', true),
- domain: clone(this.config.y.domain),
- xScale: clone(this.x),
- yScale: clone(this.y),
- brush: d3.svg
- .brush()
- .x(this.x)
- .y(this.y)
- };
- this.wrap.datum(this.package);
-
- //Define invisible brush overlay.
- this.package.overlay.append('rect').attr({
- x: 0,
- y: 0,
- width: this.plot_width,
- height: this.plot_height,
- 'fill-opacity': 0
- });
-
- //Attach additional data to SVG and marks.
- this.package.overlay.style('cursor', 'crosshair').datum({ measure: this.data.measure });
- }
-
- function handleNoData() {
- this.svg.selectAll('*').style('visibility', 'hidden');
- this.svg
- .append('text')
- .classed('no-data', true)
- .attr({
- x: 0,
- dx: -this.config.margin.left,
- y: 0,
- dy: 10
- })
- .text('No data selected.');
- }
-
- function drawNormalRange() {
- if (this.config.normal_range_method)
- this.svg
- .insert('rect', '.line-supergroup')
- .classed('normal-range', true)
- .attr({
- x: this.x(this.x_dom[0]) - 5, // make sure left side of normal range does not appear in chart
- y: this.y(this.uln()),
- width: this.plot_width + 10, // make sure right side of normal range does not appear in chart
- height: this.y(this.lln()) - this.y(this.uln()),
- 'clip-path': 'url(#' + this.id + ')'
- });
- }
-
- function annotateInliers() {
- var _this = this;
-
- this.inliersAnnotation.g.classed('hidden', this.config.inliers);
-
- if (!this.config.inliers) {
- //text
- var n = this.data.IDs.filtered.length;
- var nDisplayed = this.data.IDs.displayed.length;
- var nHidden = n - nDisplayed;
- this.inliersAnnotation.text
- .attr({
- x: 0,
- dx: -10,
- y: this.plot_height,
- dy: 19
- })
- .text('' + nHidden);
-
- //text box
- var textDimensions = this.inliersAnnotation.text.node().getBBox();
- this.inliersAnnotation.rect.attr({
- x: textDimensions.x - 2,
- y: textDimensions.y,
- width: textDimensions.width + 4,
- height: textDimensions.height
- });
-
- //tooltip
- this.inliersAnnotation.title.text(
- nHidden +
- ' of ' +
- n +
- ' participants (' +
- d3.format('%')(nHidden / n) +
- ') with entirely normal results are hidden.\nToggle the "Normal range inliers" checkbox to display these participants.'
- );
-
- //mosue hover
- this.inliersAnnotation.g
- .on('mouseover', function() {
- _this.controls.wrap
- .selectAll('.control-group')
- .filter(function(d) {
- return d.option === 'inliers';
- })
- .classed('inlier-highlighting', true);
- })
- .on('mouseout', function() {
- _this.controls.wrap
- .selectAll('.control-group')
- .filter(function(d) {
- return d.option === 'inliers';
- })
- .classed('inlier-highlighting', false);
- });
- }
- }
-
- function attachLines() {
- var _this = this;
-
- this.lines = this.svg.selectAll('.line-supergroup g.line path');
- this.lines.each(function(d, i) {
- d.id = d.values[0].values.raw[0][_this.config.id_col];
- d.lln = d.values[0].values.raw[0][_this.config.lln_col];
- d.uln = d.values[0].values.raw[0][_this.config.uln_col];
- d.lines = d.values.map(function(di, i) {
- var line;
- if (i) {
- line = {
- x0:
- _this.config.x.type === 'linear'
- ? d.values[i - 1].values.x
- : _this.x(d.values[i - 1].values.x) + _this.x.rangeBand() / 2,
- y0: d.values[i - 1].values.y,
- x1:
- _this.config.x.type === 'linear'
- ? di.values.x
- : _this.x(di.values.x) + _this.x.rangeBand() / 2,
- y1: di.values.y
- };
- }
- return line;
- });
- d.lines.shift();
- });
- }
-
- function identifyPoints() {
- this.lines
- .filter(function(d) {
- return d.lines.length === 0;
- })
- .classed('single-point', true);
- }
-
- d3.selection.prototype.moveToFront = function() {
- return this.each(function() {
- this.parentNode.appendChild(this);
- });
- };
-
- /**
- * @author Peter Kelley
- * @author pgkelley4@gmail.com
- */
-
- /**
- * See if two line segments intersect. This uses the
- * vector cross product approach described below:
- * http://stackoverflow.com/a/565282/786339
- *
- * @param {Object} p point object with x and y coordinates
- * representing the start of the 1st line.
- * @param {Object} p2 point object with x and y coordinates
- * representing the end of the 1st line.
- * @param {Object} q point object with x and y coordinates
- * representing the start of the 2nd line.
- * @param {Object} q2 point object with x and y coordinates
- * representing the end of the 2nd line.
- */
-
- /**
- * Subtract the second point from the first.
- *
- * @param {Object} point1 point object with x and y coordinates
- * @param {Object} point2 point object with x and y coordinates
- *
- * @return the subtraction result as a point object
- */
-
- function subtractPoints(point1, point2) {
- var result = {};
- result.x = point1.x - point2.x;
- result.y = point1.y - point2.y;
-
- return result;
- }
-
- /**
- * Calculate the cross product of the two points.
- *
- * @param {Object} point1 point object with x and y coordinates
- * @param {Object} point2 point object with x and y coordinates
- *
- * @return the cross product result as a float
- */
- function crossProduct(point1, point2) {
- return point1.x * point2.y - point1.y * point2.x;
- }
-
- /**
- * See if the points are equal.
- *
- * @param {Object} point1 point object with x and y coordinates
- * @param {Object} point2 point object with x and y coordinates
- *
- * @return if the points are equal
- */
- function equalPoints(point1, point2) {
- return point1.x == point2.x && point1.y == point2.y;
- }
-
- /**
- * See if all arguments are equal.
- *
- * @param {...} args arguments that will be compared by '=='.
- *
- * @return if all arguments are equal
- */
- function allEqual(args) {
- var firstValue = arguments[0],
- i;
- for (i = 1; i < arguments.length; i += 1) {
- if (arguments[i] != firstValue) {
- return false;
- }
- }
- return true;
- }
-
- function doLineSegmentsIntersect(p, p2, q, q2) {
- var r = subtractPoints(p2, p);
- var s = subtractPoints(q2, q);
-
- var uNumerator = crossProduct(subtractPoints(q, p), r);
- var denominator = crossProduct(r, s);
-
- if (uNumerator == 0 && denominator == 0) {
- // They are coLlinear
-
- // Do they touch? (Are any of the points equal?)
- if (
- equalPoints(p, q) ||
- equalPoints(p, q2) ||
- equalPoints(p2, q) ||
- equalPoints(p2, q2)
- ) {
- return true;
- }
- // Do they overlap? (Are all the point differences in either direction the same sign)
- return (
- !allEqual(q.x - p.x < 0, q.x - p2.x < 0, q2.x - p.x < 0, q2.x - p2.x < 0) ||
- !allEqual(q.y - p.y < 0, q.y - p2.y < 0, q2.y - p.y < 0, q2.y - p2.y < 0)
- );
- }
-
- if (denominator == 0) {
- // lines are paralell
- return false;
- }
-
- var u = uNumerator / denominator;
- var t = crossProduct(subtractPoints(q, p), s) / denominator;
-
- return t >= 0 && t <= 1 && u >= 0 && u <= 1;
- }
-
- function highlightChart() {
- var _this = this;
-
- var extent = this.package.brush.extent();
- var x0 = extent[0][0]; // top left x-coordinate
- var y0 = extent[1][1]; // top left y-coordinate
- var x1 = extent[1][0]; // bottom right x-coordinate
- var y1 = extent[0][1]; // bottom right y-coordinate
- var top = { x0: x1, y0: y0, x1: x0, y1: y0 };
- var right = { x0: x1, y0: y1, x1: x1, y1: y0 };
- var bottom = { x0: x0, y0: y1, x1: x1, y1: y1 };
- var left = { x0: x0, y0: y0, x1: x0, y1: y1 };
- var sides = [top, right, bottom, left];
-
- //Determine which lines fall inside the brush.
- this.lines.classed('brushed', function(d) {
- d.intersection = false;
- //lines
- if (d.lines.length) {
- d.lines.forEach(function(line) {
- sides.forEach(function(side) {
- if (!d.intersection)
- d.intersection = doLineSegmentsIntersect(
- { x: line.x0, y: line.y0 },
- { x: line.x1, y: line.y1 },
- { x: side.x0, y: side.y0 },
- { x: side.x1, y: side.y1 }
- );
- });
- });
- } else {
- //points
- var x =
- _this.config.x.type === 'linear'
- ? d.values[0].values.x
- : _this.x(d.values[0].values.x) + _this.x.rangeBand() / 2;
- var y = d.values[0].values.y;
- d.intersection = x0 <= x && x <= x1 && y1 <= y && y <= y0;
- }
-
- return d.intersection;
- });
-
- this.parent.data.IDs.selected = this.lines
- .data()
- .filter(function(d) {
- return d.intersection;
- })
- .map(function(d) {
- return d.id;
- });
- }
-
- function highlightCharts() {
- var _this = this;
-
- //Highlight brushed lines.
- this.parent.wrap
- .selectAll('.line-supergroup g.line path')
- .classed('brushed', false)
- .filter(function(d) {
- return _this.parent.data.IDs.selected.indexOf(d.id) > -1;
- })
- .classed('brushed', true)
- .each(function(d) {
- d3.select(this.parentNode).moveToFront();
- });
-
- //Draw listing displaying brushed IDs first.
- if (this.parent.data.IDs.selected.length) {
- d3.select('#Listing-nav').classed('brushed', true);
- this.parent.listing.data.raw = this.parent.data.raw.filter(function(d) {
- return _this.parent.data.IDs.selected.indexOf(d[_this.config.id_col]) > -1;
- });
- this.parent.listing.draw();
- } else {
- d3.select('#Listing-nav').classed('brushed', false);
- this.parent.listing.data.raw = this.parent.data.raw;
- this.parent.listing.draw();
- }
- }
-
- function brush() {
- var _this = this;
-
- //Highlight previously brushed points.
- if (this.parent.data.IDs.selected.length) {
- this.lines
- .filter(function(d) {
- return _this.parent.data.IDs.selected.indexOf(d.id) > -1;
- })
- .classed('brushed', true)
- .each(function() {
- d3.select(this.parentNode).moveToFront();
- });
- }
-
- //Apply brush.
- this.package.brush
- .on('brushstart', function() {
- //Clear previous brush.
- if (_this.parent.brushedChart && _this.parent.brushedMeasure !== _this.data.measure)
- _this.parent.brushedChart.package.overlay.call(
- _this.parent.brushedChart.package.brush.clear()
- );
-
- //Attach current brushed chart to parent.
- _this.parent.brushedChart = _this;
- _this.parent.brushedMeasure = _this.data.measure;
- })
- .on('brush', function() {
- //Add highlighting to brushed chart.
- highlightChart.call(_this);
- })
- .on('brushend', function() {
- _this.config.extent = _this.package.brush.extent();
-
- //Add highlighting to all charts.
- highlightCharts.call(_this);
-
- //trigger custom participantsSelected event
- _this.parent.participantsSelected = _this.parent.data.IDs.selected;
- _this.parent.events.participantsSelected.data = _this.parent.participantsSelected;
- _this.parent.wrap.node().dispatchEvent(_this.parent.events.participantsSelected);
-
- //Redraw charts in which the currently brushed ID(s) are inliers.
- if (_this.parent.data.IDs.selected.length > 0)
- _this.parent.multiples
- .filter(function(multiple) {
- return (
- _this.parent.data.IDs.selected.filter(function(ID) {
- return multiple.data.IDs.displayed.indexOf(ID) < 0;
- }).length > 0
- );
- })
- .forEach(function(multiple) {
- multiple.draw();
- });
- });
-
- //Initialize brush on brush overlay.
- this.package.overlay.call(this.package.brush);
-
- //Maintain brush on redraw.
- if (!this.config.extent) this.config.extent = this.package.brush.extent();
- if (
- (this.config.extent[0][0] !== this.package.brush.extent()[0][0] ||
- this.config.extent[0][1] !== this.package.brush.extent()[0][1] ||
- this.config.extent[1][0] !== this.package.brush.extent()[1][0] ||
- this.config.extent[1][1] !== this.package.brush.extent()[1][1]) &&
- this.data.measure === this.parent.brushedMeasure
- ) {
- if (this.config.x.type === 'ordinal') {
- this.config.extent[0][0] =
- (this.config.extent[0][0] * this.plot_width) / this.config.previous_plot_width;
- this.config.extent[1][0] =
- (this.config.extent[1][0] * this.plot_width) / this.config.previous_plot_width;
- }
- this.package.brush.extent(this.config.extent);
- this.package.overlay.call(this.package.brush);
- highlightCharts.call(this);
- }
- }
-
- function rotateXaxisTickLabels() {
- if (this.config.x.rotate_tick_labels) {
- var ticks = this.svg
- .selectAll('.' + 'x' + '.axis .tick text')
- .attr({
- transform: 'rotate(-45)',
- dx: -10,
- dy: 10
- })
- .style('text-anchor', 'end');
-
- ticks
- .filter(function(d) {
- return ('' + d).length > 10;
- })
- .style('cursor', 'help')
- .text(function(d) {
- return d.slice(0, 7) + '...';
- })
- .append('title')
- .text(function(d) {
- return d;
- });
- }
- }
-
- function onResize() {
- //Reset chart.
- resetChart.call(this);
-
- //Define datum for each multiple and attach it to multiple's container.
- definePackage.call(this);
-
- //Draw normal range.
- if (this.filtered_data.length == 0) handleNoData.call(this);
- else {
- //Draw normal range.
- drawNormalRange.call(this);
-
- //Annotate number of inliers.
- annotateInliers.call(this);
-
- //Attach lines to chart object.
- attachLines.call(this);
-
- //Identify lines with only one node.
- identifyPoints.call(this);
-
- //Add brush functionality.
- brush.call(this);
-
- //Rotate x-axis tick labels.
- rotateXaxisTickLabels.call(this);
- }
- }
-
- function onDestroy() {}
-
- var chartCallbacks = {
- onInit: onInit,
- onLayout: onLayout,
- onPreprocess: onPreprocess,
- onDatatransform: onDatatransform,
- onDraw: onDraw,
- onResize: onResize,
- onDestroy: onDestroy
- };
-
- function onInit$1() {}
-
- function onLayout$1() {}
-
- function onDraw$1() {
- var _this = this;
-
- //Highlight selected rows.
- if (this.data.filtered.length)
- this.table.selectAll('tbody tr').classed('brushed', function(d) {
- return _this.chart.data.IDs.selected.indexOf(d[_this.chart.config.id_col]) > -1;
- });
- }
-
- function onDestroy$1() {}
-
- var listingCallbacks = {
- onInit: onInit$1,
- onLayout: onLayout$1,
- onDraw: onDraw$1,
- onDestroy: onDestroy$1
- };
-
- //Utility polyfills
-
- function paneledOutlierExplorer() {
- var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'body';
- var settings = arguments[1];
-
- //Define unique div within passed element argument.
- var container = d3
- .select(element)
- .append('div')
- .attr('id', 'paneled-outlier-explorer'),
- containerElement = container.node(),
- controlsContainer = container.append('div').attr('id', 'left-side'),
- controlsContainerElement = controlsContainer.node();
-
- //Define .css styles to avoid requiring a separate .css file.
- defineStyles();
-
- //Clone, merge, and sync settings and define chart.
- var initialSettings = clone(settings),
- mergedSettings = Object.assign({}, defaultSettings, initialSettings),
- syncedSettings = syncSettings(mergedSettings),
- syncedControlInputs = syncControlInputs(controlInputs, syncedSettings),
- controls = webcharts.createControls(controlsContainerElement, {
- location: 'top',
- inputs: syncedControlInputs
- }),
- chart = webcharts.createChart(containerElement, syncedSettings, controls),
- listing = webcharts.createTable(containerElement, { nRowsPerPage: 25 }, controls);
-
- //Attach stuff to chart.
- chart.container = container;
- chart.listing = listing;
- chart.config.initialSettings = clone(syncedSettings);
-
- //Attach stuff to listing.
- listing.container = container;
- listing.chart = chart;
-
- //Define chart callbacks.
- for (var callback in chartCallbacks) {
- chart.on(callback.substring(2).toLowerCase(), chartCallbacks[callback]);
- } //Define listing callbacks.
- for (var _callback in listingCallbacks) {
- listing.on(_callback.substring(2).toLowerCase(), listingCallbacks[_callback]);
- } //Redefine chart.init() in order to call webCharts.multiply() on paneledOutlierExplorer().init().
- Object.defineProperty(chart, 'init', {
- enumerable: false,
- configurable: true,
- writable: true,
- value: init
- });
-
- return chart;
- }
-
- return paneledOutlierExplorer;
-});
diff --git a/inst/htmlwidgets/lib/safety-delta-delta-1.0.0/safetyDeltaDelta.js b/inst/htmlwidgets/lib/safety-delta-delta-1.0.0/safetyDeltaDelta.js
deleted file mode 100644
index 5b532a84..00000000
--- a/inst/htmlwidgets/lib/safety-delta-delta-1.0.0/safetyDeltaDelta.js
+++ /dev/null
@@ -1,1851 +0,0 @@
-(function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined'
- ? (module.exports = factory(require('d3'), require('webcharts')))
- : typeof define === 'function' && define.amd
- ? define(['d3', 'webcharts'], factory)
- : ((global = global || self),
- (global.safetyDeltaDelta = factory(global.d3, global.webCharts)));
-})(this, function (d3, webcharts) {
- 'use strict';
-
- if (typeof Object.assign != 'function') {
- Object.defineProperty(Object, 'assign', {
- value: function assign(target, varArgs) {
- if (target == null) {
- // TypeError if undefined or null
- throw new TypeError('Cannot convert undefined or null to object');
- }
-
- var to = Object(target);
-
- for (var index = 1; index < arguments.length; index++) {
- var nextSource = arguments[index];
-
- if (nextSource != null) {
- // Skip over if undefined or null
- for (var nextKey in nextSource) {
- // Avoid bugs when hasOwnProperty is shadowed
- if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
- to[nextKey] = nextSource[nextKey];
- }
- }
- }
- }
-
- return to;
- },
- writable: true,
- configurable: true
- });
- }
-
- if (!Array.prototype.find) {
- Object.defineProperty(Array.prototype, 'find', {
- value: function value(predicate) {
- // 1. Let O be ? ToObject(this value).
- if (this == null) {
- throw new TypeError('"this" is null or not defined');
- }
-
- var o = Object(this);
-
- // 2. Let len be ? ToLength(? Get(O, 'length')).
- var len = o.length >>> 0;
-
- // 3. If IsCallable(predicate) is false, throw a TypeError exception.
- if (typeof predicate !== 'function') {
- throw new TypeError('predicate must be a function');
- }
-
- // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
- var thisArg = arguments[1];
-
- // 5. Let k be 0.
- var k = 0;
-
- // 6. Repeat, while k < len
- while (k < len) {
- // a. Let Pk be ! ToString(k).
- // b. Let kValue be ? Get(O, Pk).
- // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)).
- // d. If testResult is true, return kValue.
- var kValue = o[k];
- if (predicate.call(thisArg, kValue, k, o)) {
- return kValue;
- }
- // e. Increase k by 1.
- k++;
- }
-
- // 7. Return undefined.
- return undefined;
- }
- });
- }
-
- if (!Array.prototype.findIndex) {
- Object.defineProperty(Array.prototype, 'findIndex', {
- value: function value(predicate) {
- // 1. Let O be ? ToObject(this value).
- if (this == null) {
- throw new TypeError('"this" is null or not defined');
- }
-
- var o = Object(this);
-
- // 2. Let len be ? ToLength(? Get(O, "length")).
- var len = o.length >>> 0;
-
- // 3. If IsCallable(predicate) is false, throw a TypeError exception.
- if (typeof predicate !== 'function') {
- throw new TypeError('predicate must be a function');
- }
-
- // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
- var thisArg = arguments[1];
-
- // 5. Let k be 0.
- var k = 0;
-
- // 6. Repeat, while k < len
- while (k < len) {
- // a. Let Pk be ! ToString(k).
- // b. Let kValue be ? Get(O, Pk).
- // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)).
- // d. If testResult is true, return k.
- var kValue = o[k];
- if (predicate.call(thisArg, kValue, k, o)) {
- return k;
- }
- // e. Increase k by 1.
- k++;
- }
-
- // 7. Return -1.
- return -1;
- }
- });
- }
-
- Math.log10 = Math.log10 =
- Math.log10 ||
- function (x) {
- return Math.log(x) * Math.LOG10E;
- };
-
- // https://github.com/wbkd/d3-extended
- d3.selection.prototype.moveToFront = function () {
- return this.each(function () {
- this.parentNode.appendChild(this);
- });
- };
-
- d3.selection.prototype.moveToBack = function () {
- return this.each(function () {
- var firstChild = this.parentNode.firstChild;
- if (firstChild) {
- this.parentNode.insertBefore(this, firstChild);
- }
- });
- };
-
- function rendererSettings() {
- return {
- id_col: 'USUBJID',
- visit_col: 'VISIT',
- visitn_col: 'VISITNUM',
- measure_col: 'TEST',
- value_col: 'STRESN',
- filters: null,
- details: null,
- measure: {
- x: null,
- y: null
- },
- visits: {
- baseline: [],
- comparison: [],
- stat: 'mean'
- },
- add_regression_line: true
- };
- }
-
- function webchartsSettings() {
- return {
- x: {
- column: null,
- type: 'linear',
- label: 'x delta',
- format: '0.2f'
- },
- y: {
- column: null,
- type: 'linear',
- label: 'y delta',
- behavior: 'flex',
- format: '0.2f'
- },
- marks: [
- {
- type: 'circle',
- per: null,
- radius: 4,
- attributes: {
- 'stroke-width': 0.5,
- 'fill-opacity': 0.8
- },
- tooltip:
- 'Subject ID: [key]\nX Delta: [delta_x_rounded]\nY Delta: [delta_y_rounded]'
- }
- ],
- gridlines: 'xy',
- resizable: false,
- margin: { right: 25, top: 25 },
- aspect: 1,
- width: 400
- };
- }
-
- function syncSettings(settings) {
- //handle a string argument to filters
- if (!(settings.filters instanceof Array))
- settings.filters = typeof settings.filters === 'string' ? [settings.filters] : [];
-
- //handle a string argument to details
- if (!(settings.details instanceof Array))
- settings.details = typeof settings.details === 'string' ? [settings.details] : [];
-
- //Define default details.
- var defaultDetails = [{ value_col: settings.id_col, label: 'Participant ID' }];
- if (Array.isArray(settings.filters))
- settings.filters
- .filter(function (filter) {
- return filter.value_col !== settings.id_col;
- })
- .forEach(function (filter) {
- return defaultDetails.push({
- value_col: filter.value_col ? filter.value_col : filter,
- label: filter.label
- ? filter.label
- : filter.value_col
- ? filter.value_col
- : filter
- });
- });
-
- //If [settings.details] is not specified:
- if (!settings.details) settings.details = defaultDetails;
- else {
- //If [settings.details] is specified:
- //Allow user to specify an array of columns or an array of objects with a column property
- //and optionally a column label.
- settings.details.forEach(function (detail) {
- if (
- defaultDetails
- .map(function (d) {
- return d.value_col;
- })
- .indexOf(detail.value_col ? detail.value_col : detail) === -1
- )
- defaultDetails.push({
- value_col: detail.value_col ? detail.value_col : detail,
- label: detail.label
- ? detail.label
- : detail.value_col
- ? detail.value_col
- : detail
- });
- });
- settings.details = defaultDetails;
- }
-
- return settings;
- }
-
- function controlInputs() {
- return [
- {
- type: 'dropdown',
- values: [],
- label: 'Baseline visit(s)',
- option: 'visits.baseline',
- require: true,
- multiple: true
- },
- {
- type: 'dropdown',
- values: [],
- label: 'Comparison visit(s)',
- option: 'visits.comparison',
- require: true,
- multiple: true
- },
- {
- type: 'dropdown',
- values: [],
- label: 'X Measure',
- option: 'measure.x',
- require: true
- },
- {
- type: 'dropdown',
- values: [],
- label: 'Y Measure',
- option: 'measure.y',
- require: true
- }
- ];
- }
-
- function syncControlInputs(controlInputs, settings) {
- //Add filters to default controls.
- if (Array.isArray(settings.filters) && settings.filters.length > 0) {
- settings.filters.forEach(function (filter) {
- var filterObj = {
- type: 'subsetter',
- value_col: filter.value_col || filter,
- label: filter.label || filter.value_col || filter
- };
- controlInputs.push(filterObj);
- });
- } else delete settings.filters;
- return controlInputs;
- }
-
- function listingSettings() {
- return {
- cols: ['key', 'spark', 'delta'],
- headers: ['Measure', '', 'Change over Time'],
- searchable: false,
- sortable: false,
- pagination: false,
- exportable: false
- };
- }
-
- var configuration = {
- rendererSettings: rendererSettings,
- webchartsSettings: webchartsSettings,
- settings: Object.assign({}, rendererSettings(), webchartsSettings()),
- syncSettings: syncSettings,
- controlInputs: controlInputs,
- syncControlInputs: syncControlInputs,
- listingSettings: listingSettings
- };
-
- function cleanData() {
- var _this = this;
-
- //Remove missing and non-numeric data.
- var preclean = this.raw_data;
- var clean = this.raw_data.filter(function (d) {
- return /^-?[0-9.]+$/.test(d[_this.config.value_col]);
- });
- var nPreclean = preclean.length;
- var nClean = clean.length;
- var nRemoved = nPreclean - nClean;
-
- //Warn user of removed records.
- if (nRemoved > 0)
- console.warn(
- nRemoved +
- ' missing or non-numeric result' +
- (nRemoved > 1 ? 's have' : ' has') +
- ' been removed.'
- );
-
- //Preserve cleaned data.
- this.initial_data = clean;
- }
-
- function trimMeasures() {
- var _this = this;
-
- this.initial_data.forEach(function (d) {
- d[_this.config.measure_col] = d[_this.config.measure_col].trim();
- });
- }
-
- function checkFilters() {
- var _this = this;
-
- if (this.config.filters)
- this.config.filters = this.config.filters.filter(function (filter) {
- var variableExists = _this.raw_data[0].hasOwnProperty(filter.value_col);
- var nLevels = d3
- .set(
- _this.raw_data.map(function (d) {
- return d[filter.value_col];
- })
- )
- .values().length;
-
- if (!variableExists)
- console.warn(
- ' The [ ' +
- filter.label +
- ' ] filter has been removed because the variable does not exist.'
- );
- else if (nLevels < 2)
- console.warn(
- 'The [ ' +
- filter.label +
- ' ] filter has been removed because the variable has only one level.'
- );
-
- return variableExists && nLevels > 1;
- });
- }
-
- function getMeasures() {
- var _this = this;
-
- this.measures = d3
- .set(
- this.initial_data.map(function (d) {
- return d[_this.config.measure_col];
- })
- )
- .values()
- .sort();
- }
-
- function getVisits() {
- var _this = this;
-
- if (this.config.visitn_col && this.initial_data[0].hasOwnProperty(this.config.visitn_col))
- this.visits = d3
- .set(
- this.initial_data.map(function (d) {
- return d[_this.config.visit_col] + '||' + d[_this.config.visitn_col];
- })
- )
- .values()
- .sort(function (a, b) {
- var aSplit = a.split('||');
- var aVisit = aSplit[0];
- var aOrder = aSplit[1];
- var bSplit = b.split('||');
- var bVisit = bSplit[0];
- var bOrder = bSplit[1];
- var diff = aOrder - bOrder;
- return diff
- ? diff
- : aOrder < bOrder
- ? -1
- : aOrder > bOrder
- ? 1
- : aVisit < bVisit
- ? -1
- : 1;
- })
- .map(function (visit) {
- return visit.split('||')[0];
- });
- else
- this.visits = d3
- .set(
- this.initial_data.map(function (d) {
- return d[_this.config.visit_col];
- })
- )
- .values()
- .sort();
- }
-
- function updateControlInputs() {
- var x_control = this.controls.config.inputs.find(function (input) {
- return input.option === 'measure.x';
- });
- x_control.values = this.measures;
- x_control.start = this.config.measure.x;
-
- var y_control = this.controls.config.inputs.find(function (input) {
- return input.option === 'measure.y';
- });
- y_control.values = this.measures;
- y_control.start = this.config.measure.y;
-
- var baseline_control = this.controls.config.inputs.find(function (input) {
- return input.option === 'visits.baseline';
- });
- baseline_control.values = this.visits;
- baseline_control.start = this.config.visits.baseline;
-
- var comparison_control = this.controls.config.inputs.find(function (input) {
- return input.option === 'visits.comparison';
- });
- comparison_control.values = this.visits;
- comparison_control.start = this.config.visits.comprarison;
- }
-
- function initCustomEvents() {
- var chart = this;
- chart.participantsSelected = [];
- chart.events.participantsSelected = new CustomEvent('participantsSelected');
- }
-
- function initSettings() {
- //Set initial measures.
- this.config.measure.x = this.config.measure.x || this.measures[0];
-
- // this.config.x.column = this.config.measure.x;
- this.config.measure.y = this.config.measure.y || this.measures[1];
-
- //Set baseline and comparison visits.
- this.config.visits.baseline =
- this.config.visits.baseline.length > 0 ? this.config.visits.baseline : [this.visits[0]];
-
- this.config.visits.comparison =
- this.config.visits.comparison.length > 0
- ? this.config.visits.comparison
- : [this.visits[this.visits.length - 1]];
- }
-
- function onInit() {
- // 1. Remove invalid data.
- cleanData.call(this);
-
- // 2. trim measures.
- trimMeasures.call(this);
-
- // 3a Check filters against data.
- checkFilters.call(this);
-
- // 3b Get list of measures.
- getMeasures.call(this);
-
- // 3c Get list of visits.
- getVisits.call(this);
-
- //4a. Initialize the delta-delta settings & Update control inputs.
- initSettings.call(this);
- updateControlInputs.call(this);
-
- //initialize custom events
- initCustomEvents.call(this);
- }
-
- function initNotes() {
- //Add footnote element.
- this.wrap
- .insert('p', ':first-child')
- .attr('class', 'record-note')
- .style('text-align', 'center')
- .style('font-weight', 'bold')
- .text('Click a point to see details.');
-
- //Add header element in which to list visits at which measure is captured.
- this.wrap.append('p', 'svg').attr('class', 'possible-visits');
-
- //Add element for participant counts.
- this.controls.wrap
- .append('em')
- .classed('annote', true)
- .style('display', 'block');
- }
-
- function updateVisitControls() {
- var _this = this;
-
- var config = this.config;
- var baselineSelect = this.controls.wrap
- .selectAll('.control-group')
- .filter(function (f) {
- return f.option === 'visits.baseline';
- })
- .select('select');
- baselineSelect
- .selectAll('option')
- .filter(function (f) {
- return _this.config.visits.baseline.indexOf(f) > -1;
- })
- .attr('selected', 'selected');
-
- var comparisonSelect = this.controls.wrap
- .selectAll('.control-group')
- .filter(function (f) {
- return f.option === 'visits.comparison';
- })
- .select('select');
- comparisonSelect
- .selectAll('option')
- .filter(function (f) {
- return _this.config.visits.comparison.indexOf(f) > -1;
- })
- .attr('selected', 'selected');
- }
-
- function onLayout() {
- initNotes.call(this);
- updateVisitControls.call(this);
- }
-
- function addParticipantLevelMetadata(d, participant_obj) {
- var varList = [];
- if (this.config.filters) {
- var filterVars = this.config.filters.map(function (d) {
- return d.hasOwnProperty('value_col') ? d.value_col : d;
- });
- varList = d3.merge([varList, filterVars]);
- }
- if (this.config.group_cols) {
- var groupVars = this.config.group_cols.map(function (d) {
- return d.hasOwnProperty('value_col') ? d.value_col : d;
- });
- varList = d3.merge([varList, groupVars]);
- }
- if (this.config.details) {
- var detailVars = this.config.details.map(function (d) {
- return d.hasOwnProperty('value_col') ? d.value_col : d;
- });
- varList = d3.merge([varList, detailVars]);
- }
-
- varList.forEach(function (v) {
- participant_obj[v] = '' + d[0][v];
- });
- }
-
- function getMeasureDetails(pt_data) {
- var config = this.config;
- var measure_details = d3
- .nest()
- .key(function (d) {
- return d[config.measure_col];
- })
- .rollup(function (di) {
- var measure_obj = {};
- measure_obj.key = di[0][config.measure_col];
- measure_obj.spark = 'sparkline placeholder';
- measure_obj.toggle = '+';
- measure_obj.raw = di;
- measure_obj.axisFlag =
- measure_obj.key == config.measure.x
- ? 'X'
- : measure_obj.key == config.measure.y
- ? 'Y'
- : '';
- measure_obj.raw.forEach(function (dii) {
- dii.baseline = config.visits.baseline.indexOf(dii[config.visit_col]) > -1;
- dii.comparison = config.visits.comparison.indexOf(dii[config.visit_col]) > -1;
- dii.color = dii.baseline ? 'blue' : dii.comparison ? 'orange' : '#999';
- });
-
- ['baseline', 'comparison'].forEach(function (t) {
- measure_obj[t + '_records'] = di.filter(function (f) {
- return config.visits[t].indexOf(f[config.visit_col]) > -1;
- });
-
- measure_obj[t + '_value'] = d3.mean(measure_obj[t + '_records'], function (d) {
- return d[config.value_col];
- });
- });
- measure_obj['delta'] = measure_obj.comparison_value - measure_obj.baseline_value;
- return measure_obj;
- })
- .entries(pt_data);
- measure_details = measure_details
- .map(function (m) {
- return m.values;
- })
- .sort(function (a, b) {
- if (a.axisFlag == 'X') return -1;
- else if (b.axisFlag == 'X') return 1;
- else if (a.axisFlag == 'Y') return -1;
- else if (b.axisFlag == 'Y') return 1;
- else if (a.key < b.key) return -1;
- else if (b.key > a.key) return 1;
- else return 0;
- });
- return measure_details;
- }
-
- function flattenData(rawData) {
- var _this = this;
-
- var nested = d3
- .nest()
- .key(function (d) {
- return d[_this.config.id_col];
- })
- .rollup(function (d) {
- var obj = {};
- obj.key = d[0][_this.config.id_col];
- obj.raw = d;
- obj.measures = getMeasureDetails.call(_this, d);
-
- obj.x_details = obj.measures.find(function (f) {
- return f.key == _this.config.measure.x;
- });
- obj.delta_x = obj.x_details ? obj.x_details.delta : null;
- obj.delta_x_rounded = obj.x_details ? d3.format('0.2f')(obj.delta_x) : '';
-
- obj.y_details = obj.measures.find(function (f) {
- return f.key == _this.config.measure.y;
- });
- obj.delta_y = obj.y_details ? obj.y_details.delta : null;
- obj.delta_y_rounded = obj.y_details ? d3.format('0.2f')(obj.delta_y) : '';
-
- addParticipantLevelMetadata.call(_this, d, obj);
-
- return obj;
- })
- .entries(rawData);
-
- return nested.map(function (m) {
- return m.values;
- });
- }
-
- function updateAxisSettings() {
- var config = this.config;
-
- //set config properties here since they aren't available in onInit
- config.x.column = 'delta_x';
- config.y.column = 'delta_y';
- config.marks[0].per = ['key'];
-
- config.x.label = 'Change in ' + config.measure.x;
- config.y.label = 'Change in ' + config.measure.y;
- }
-
- function onPreprocess() {
- updateAxisSettings.call(this);
- this.raw_data = flattenData.call(this, this.initial_data);
- }
-
- function onDatatransform() { }
-
- /*------------------------------------------------------------------------------------------------\
- Annotate number of participants based on current filters, number of participants in all, and
- the corresponding percentage.
-
- Inputs:
-
- chart - a webcharts chart object
- id_unit - a text string to label the units in the annotation (default = 'participants')
- selector - css selector for the annotation
- \------------------------------------------------------------------------------------------------*/
-
- function updateParticipantCount(chart, selector, id_unit) {
- //count the number of unique ids in the data set
- var totalObs = d3
- .set(
- chart.initial_data.map(function (d) {
- return d[chart.config.id_col];
- })
- )
- .values().length;
-
- //count the number of unique ids in the current chart and calculate the percentage
- var currentObs = chart.filtered_data.filter(function (f) {
- return (
- !isNaN(f.delta_x) && f.delta_x !== null && !isNaN(f.delta_y) && f.delta_y !== null
- );
- }).length; // TODO: remove these records as part of the data flow
-
- var percentage = d3.format('0.1%')(currentObs / totalObs);
-
- //clear the annotation
- var annotation = d3.select(selector);
- annotation.selectAll('*').remove();
-
- //update the annotation
- var units = id_unit ? ' ' + id_unit : ' participant(s)';
- annotation.text(currentObs + ' of ' + totalObs + units + ' shown (' + percentage + ')');
- }
-
- function reset() {
- this.svg.selectAll('g.boxplot').remove();
- this.svg
- .selectAll('g.point')
- .classed('selected', false)
- .select('circle')
- .style('fill', this.config.colors[0]);
- this.wrap
- .select('.record-note')
- .style('text-align', 'center')
- .text('Click a point to see details.');
- this.listing.draw([]);
- this.listing.wrap.style('display', 'none');
- }
-
- function onDraw() {
- //Annotate selected and total number of participants.
- updateParticipantCount(this, '.annote');
-
- //Reset things.
- reset.call(this);
- }
-
- function drawBoxPlot(
- svg,
- results,
- height,
- width,
- domain,
- boxPlotWidth,
- boxColor,
- boxInsideColor,
- fmt,
- horizontal
- ) {
- //set default orientation to "horizontal"
- var horizontal = horizontal == undefined ? true : horizontal;
-
- //make the results numeric and sort
- var results = results
- .map(function (d) {
- return +d;
- })
- .sort(d3.ascending);
-
- //set up scales
- var y = d3.scale.linear().range([height, 0]);
-
- var x = d3.scale.linear().range([0, width]);
-
- if (horizontal) {
- y.domain(domain);
- } else {
- x.domain(domain);
- }
-
- var probs = [0.05, 0.25, 0.5, 0.75, 0.95];
- for (var i = 0; i < probs.length; i++) {
- probs[i] = d3.quantile(results, probs[i]);
- }
-
- var boxplot = svg
- .append('g')
- .attr('class', 'boxplot')
- .datum({ values: results, probs: probs });
-
- //draw rectangle from q1 to q3
- var box_x = horizontal ? x(0.5 - boxPlotWidth / 2) : x(probs[1]);
- var box_width = horizontal
- ? x(0.5 + boxPlotWidth / 2) - x(0.5 - boxPlotWidth / 2)
- : x(probs[3]) - x(probs[1]);
- var box_y = horizontal ? y(probs[3]) : y(0.5 + boxPlotWidth / 2);
- var box_height = horizontal
- ? -y(probs[3]) + y(probs[1])
- : y(0.5 - boxPlotWidth / 2) - y(0.5 + boxPlotWidth / 2);
-
- boxplot
- .append('rect')
- .attr('class', 'boxplot fill')
- .attr('x', box_x)
- .attr('width', box_width)
- .attr('y', box_y)
- .attr('height', box_height)
- .style('fill', boxColor);
-
- //draw dividing lines at median, 95% and 5%
- var iS = [0, 2, 4];
- var iSclass = ['', 'median', ''];
- var iSColor = [boxColor, boxInsideColor, boxColor];
- for (var i = 0; i < iS.length; i++) {
- boxplot
- .append('line')
- .attr('class', 'boxplot ' + iSclass[i])
- .attr('x1', horizontal ? x(0.5 - boxPlotWidth / 2) : x(probs[iS[i]]))
- .attr('x2', horizontal ? x(0.5 + boxPlotWidth / 2) : x(probs[iS[i]]))
- .attr('y1', horizontal ? y(probs[iS[i]]) : y(0.5 - boxPlotWidth / 2))
- .attr('y2', horizontal ? y(probs[iS[i]]) : y(0.5 + boxPlotWidth / 2))
- .style('fill', iSColor[i])
- .style('stroke', iSColor[i]);
- }
-
- //draw lines from 5% to 25% and from 75% to 95%
- var iS = [[0, 1], [3, 4]];
- for (var i = 0; i < iS.length; i++) {
- boxplot
- .append('line')
- .attr('class', 'boxplot')
- .attr('x1', horizontal ? x(0.5) : x(probs[iS[i][0]]))
- .attr('x2', horizontal ? x(0.5) : x(probs[iS[i][1]]))
- .attr('y1', horizontal ? y(probs[iS[i][0]]) : y(0.5))
- .attr('y2', horizontal ? y(probs[iS[i][1]]) : y(0.5))
- .style('stroke', boxColor);
- }
-
- boxplot
- .append('circle')
- .attr('class', 'boxplot mean')
- .attr('cx', horizontal ? x(0.5) : x(d3.mean(results)))
- .attr('cy', horizontal ? y(d3.mean(results)) : y(0.5))
- .attr('r', horizontal ? x(boxPlotWidth / 3) : y(1 - boxPlotWidth / 3))
- .style('fill', boxInsideColor)
- .style('stroke', boxColor);
-
- boxplot
- .append('circle')
- .attr('class', 'boxplot mean')
- .attr('cx', horizontal ? x(0.5) : x(d3.mean(results)))
- .attr('cy', horizontal ? y(d3.mean(results)) : y(0.5))
- .attr('r', horizontal ? x(boxPlotWidth / 6) : y(1 - boxPlotWidth / 6))
- .style('fill', boxColor)
- .style('stroke', 'None');
-
- var formatx = fmt ? d3.format(fmt) : d3.format('.2f');
-
- boxplot
- .selectAll('.boxplot')
- .append('title')
- .text(function (d) {
- return (
- 'N = ' +
- d.values.length +
- '\n' +
- 'Min = ' +
- d3.min(d.values) +
- '\n' +
- '5th % = ' +
- formatx(d3.quantile(d.values, 0.05)) +
- '\n' +
- 'Q1 = ' +
- formatx(d3.quantile(d.values, 0.25)) +
- '\n' +
- 'Median = ' +
- formatx(d3.median(d.values)) +
- '\n' +
- 'Q3 = ' +
- formatx(d3.quantile(d.values, 0.75)) +
- '\n' +
- '95th % = ' +
- formatx(d3.quantile(d.values, 0.95)) +
- '\n' +
- 'Max = ' +
- d3.max(d.values) +
- '\n' +
- 'Mean = ' +
- formatx(d3.mean(d.values)) +
- '\n' +
- 'StDev = ' +
- formatx(d3.deviation(d.values))
- );
- });
- }
-
- function addBoxPlots() {
- // Y-axis box plot
- var yValues = this.current_data.map(function (d) {
- return d.values.y;
- });
- var ybox = this.svg.append('g').attr('class', 'yMargin');
- drawBoxPlot(ybox, yValues, this.plot_height, 1, this.y_dom, 10, '#bbb', 'white');
- ybox.select('g.boxplot').attr(
- 'transform',
- 'translate(' + (this.plot_width + this.config.margin.right / 2) + ',0)'
- );
-
- //X-axis box plot
- var xValues = this.current_data.map(function (d) {
- return d.values.x;
- });
- var xbox = this.svg.append('g').attr('class', 'xMargin');
- drawBoxPlot(
- xbox, //svg element
- xValues, //values
- 1, //height
- this.plot_width, //width
- this.x_dom, //domain
- 10, //box plot width
- '#bbb', //box color
- 'white', //detail color
- '0.2f', //format
- false // horizontal?
- );
- xbox.select('g.boxplot').attr(
- 'transform',
- 'translate(0,' + -(this.config.margin.top / 2) + ')'
- );
- }
-
- function updateClipPath() {
- //embiggen clip-path so points aren't clipped
- var radius = this.config.marks.find(function (mark) {
- return mark.type === 'circle';
- }).radius;
- this.svg
- .select('.plotting-area')
- .attr('width', this.plot_width + radius * 2 + 2) // plot width + circle radius * 2 + circle stroke width * 2
- .attr('height', this.plot_height + radius * 2 + 2) // plot height + circle radius * 2 + circle stroke width * 2
- .attr(
- 'transform',
- 'translate(-' +
- (radius + 1) + // translate left circle radius + circle stroke width
- ',-' +
- (radius + 1) + // translate up circle radius + circle stroke width
- ')'
- );
- }
-
- function addSparkLines(d) {
- var chart = this.chart;
- var config = this.chart.config;
-
- if (this.data.raw.length > 0) {
- //don't try to draw sparklines if the table is empty
- this.tbody
- .selectAll('tr')
- .style('background', 'none')
- .style('border-bottom', '.5px solid black')
- .each(function (row_d) {
- //Spark line cell
- var cell = d3
- .select(this)
- .select('td.spark')
- .classed('minimized', true)
- .text(''),
- toggle = d3
- .select(this)
- .select('td.toggle')
- .html('▽')
- .style('cursor', 'pointer')
- .style('color', '#999')
- .style('vertical-align', 'middle'),
- width = 100,
- height = 25,
- offset = 4,
- overTime = row_d.raw.sort(function (a, b) {
- return +a[config.visitn_col] - +b[config.visitn_col];
- });
-
- var x = d3.scale
- .linear()
- .domain(
- d3.extent(overTime, function (m) {
- return +m[config.visitn_col];
- })
- )
- .range([offset, width - offset]);
-
- //y-domain includes 99th population percentile + any participant outliers
- var y = d3.scale
- .linear()
- .domain(
- d3.extent(overTime, function (m) {
- return +m[config.value_col];
- })
- )
- .range([height - offset, offset]);
-
- //render the svg
- var canvas = cell
- .append('svg')
- .attr({
- width: width,
- height: height
- })
- .append('g');
-
- //draw the sparkline
- var draw_sparkline = d3.svg
- .line()
- .interpolate('linear')
- .x(function (d) {
- return x(d[config.visitn_col]);
- })
- .y(function (d) {
- return y(d[config.value_col]);
- });
- var sparkline = canvas
- .append('path')
- .datum(overTime)
- .attr({
- class: 'sparkLine',
- d: draw_sparkline,
- fill: 'none',
- stroke: '#999'
- });
-
- //draw baseline values
-
- var circles = canvas
- .selectAll('circle')
- .data(overTime)
- .enter()
- .append('circle')
- .attr('class', 'circle outlier')
- .attr('cx', function (d) {
- return x(d[config.visitn_col]);
- })
- .attr('cy', function (d) {
- return y(d[config.value_col]);
- })
- .attr('r', '2px')
- .attr('stroke', function (d) {
- return d.color;
- })
- .attr('fill', function (d) {
- return d.color == '#999' ? 'transparent' : d.color;
- })
- .append('title')
- .text(function (d) {
- return (
- 'Value = ' +
- d[config.value_col] +
- ' @ Visit ' +
- d[config.visitn_col]
- );
- });
- });
- }
- }
-
- function addFootnote() {
- this.wrap.select('span.footnote').remove();
- this.wrap
- .append('span')
- .attr('class', 'footnote')
- .style('font-size', '0.7em')
- .style('color', '#999')
- .text(
- 'This table shows all lab values collected for the selected participant. Filled blue and orange circles indicate baseline and comparison visits respectively - all other visits are draw for reference using with empty gray circles. Change over time values greater than 0 are shown in green; values less than 0 shown in red.'
- );
- }
-
- function formatDelta() {
- this.tbody
- .selectAll('tr')
- .select('td.delta')
- .text(function (d) {
- return isNaN(d.delta) ? 'NA' : d3.format('+0.2f')(d.delta);
- })
- .style('color', function (d) {
- return isNaN(d.delta)
- ? '#ccc'
- : d.delta > 0
- ? 'green'
- : d.delta < 0
- ? 'red'
- : '#999';
- });
- }
-
- function addAxisFlag() {
- var table = this;
- ['X', 'Y'].forEach(function (axis) {
- var cell = table.tbody
- .selectAll('tr')
- .filter(function (d) {
- return d.axisFlag == axis;
- })
- .select('td.key')
- .text('');
-
- cell.append('span')
- .attr('class', 'sdd-axisLabel')
- .text(axis + '-axis');
-
- cell.append('span').text(function (d) {
- return d.key;
- });
- });
- }
-
- function showParticipantDetails(d) {
- var table = this;
- var chart = this.chart;
- var raw = d.raw[0];
-
- //show detail variables in a ul
- table.wrap.select('ul.pdd-pt-details').remove();
- var ul = table.wrap
- .insert('ul', '*')
- .attr('class', 'pdd-pt-details')
- .style('list-style', 'none')
- .style('padding', '0');
-
- var lis = ul
- .selectAll('li')
- .data(chart.config.details)
- .enter()
- .append('li')
- .style('', 'block')
- .style('display', 'inline-block')
- .style('text-align', 'center')
- .style('padding', '0.5em');
-
- lis.append('div')
- .text(function (d) {
- return d.label;
- })
- .attr('div', 'label')
- .style('font-size', '0.8em');
-
- lis.append('div')
- .text(function (d) {
- return raw[d.value_col];
- })
- .attr('div', 'value');
- }
-
- function drawMeasureTable(d) {
- var chart = this;
- var config = this.config;
-
- var point_data = d.values.raw[0];
- chart.listing.wrap.style('display', null);
-
- chart.listing.on('draw', function () {
- showParticipantDetails.call(this, point_data);
- addSparkLines.call(this);
- formatDelta.call(this);
- addAxisFlag.call(this);
- addFootnote.call(this);
-
- this.thead.style('border-top', '2px solid black');
- });
- chart.listing.draw(point_data.measures);
- }
-
- function addPointClick() {
- var chart = this;
- var config = this.config;
- var points = this.marks[0].circles;
-
- points.on('click', function (d) {
- points
- .attr('stroke', function (d) {
- return chart.colorScale(d.values.raw[0][config.color_by]);
- })
- .attr('stroke-width', 0.5);
-
- d3.select(this)
- .attr('stroke-width', 3)
- .attr('stroke', 'black');
- drawMeasureTable.call(chart, d);
- });
- }
-
- var commonjsGlobal =
- typeof globalThis !== 'undefined'
- ? globalThis
- : typeof window !== 'undefined'
- ? window
- : typeof global !== 'undefined'
- ? global
- : typeof self !== 'undefined'
- ? self
- : {};
-
- function createCommonjsModule(fn, module) {
- return (module = { exports: {} }), fn(module, module.exports), module.exports;
- }
-
- var regression = createCommonjsModule(function (module, exports) {
- (function (global, factory) {
- {
- factory(module);
- }
- })(commonjsGlobal, function (module) {
- function _defineProperty(obj, key, value) {
- if (key in obj) {
- Object.defineProperty(obj, key, {
- value: value,
- enumerable: true,
- configurable: true,
- writable: true
- });
- } else {
- obj[key] = value;
- }
-
- return obj;
- }
-
- var _extends =
- Object.assign ||
- function (target) {
- for (var i = 1; i < arguments.length; i++) {
- var source = arguments[i];
-
- for (var key in source) {
- if (Object.prototype.hasOwnProperty.call(source, key)) {
- target[key] = source[key];
- }
- }
- }
-
- return target;
- };
-
- function _toConsumableArray(arr) {
- if (Array.isArray(arr)) {
- for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) {
- arr2[i] = arr[i];
- }
-
- return arr2;
- } else {
- return Array.from(arr);
- }
- }
-
- var DEFAULT_OPTIONS = { order: 2, precision: 2, period: null };
-
- /**
- * Determine the coefficient of determination (r^2) of a fit from the observations
- * and predictions.
- *
- * @param {Array
Equation: ' +
- result.string +
- '
R2: ' +
- d3.format('0.2f')(result.r2)
- );
- }
- }
-
- function onResize() {
- addBoxPlots.call(this);
- updateClipPath.call(this);
- addPointClick.call(this);
- addRegressionLine.call(this);
- }
-
- function onDestroy() { }
-
- var callbacks = {
- onInit: onInit,
- onLayout: onLayout,
- onPreprocess: onPreprocess,
- onDatatransform: onDatatransform,
- onDraw: onDraw,
- onResize: onResize,
- onDestroy: onDestroy
- };
-
- function layout(element) {
- var container = d3.select(element);
- container
- .append('div')
- .classed('sdd-component', true)
- .attr('id', 'sdd-controls');
- container
- .append('div')
- .classed('sdd-component', true)
- .attr('id', 'sdd-chart');
- container
- .append('div')
- .classed('sdd-component', true)
- .attr('id', 'sdd-listing');
- }
-
- function styles() {
- var styles = [
- '#safety-delta-delta {' + ' width: 100%;' + ' display: inline-block;' + '}',
- '.sdd-component {' +
- ' margin: 0;' +
- ' border: none;' +
- ' padding: 0;' +
- ' display: inline-block;' +
- '}',
-
- //controls
- '#sdd-controls {' + ' width: 25%;' + ' float: left;' + '}',
- '#sdd-controls .control-group {' +
- ' width: 98%;' +
- ' margin: 0 2% 5px 0;' +
- ' padding: 0;' +
- '}',
- '#sdd-controls .control-group > * {' + ' display: inline-block;' + '}',
- '#sdd-controls .changer {' + ' float: right;' + ' width: 50%;' + '}',
- '#sdd-controls .wc-control-label {' +
- ' text-align: right;' +
- ' width: 48%;' +
- '}',
- '#sdd-controls .annote {' + ' width: 98%;' + ' text-align: right;' + '}',
-
- //chart
- '#sdd-chart {' + ' width: 36%;' + ' margin: 0 2%;' + '}',
- '.wc-data-mark {' + ' cursor: pointer;' + '}',
- '.wc-data-mark:hover {' + ' stroke: black;' + ' stroke-width: 3;' + '}',
- '.regression-note {' +
- //' font-size: 0.8em;' +
- ' color: #999;' +
- '}',
-
- //listing
- '#sdd-listing {' + ' width: 35%;' + ' float: right;' + '}',
- '#sdd-listing .wc-table table {' + ' width: 100%;' + ' display: table;' + '}',
- '#sdd-listing .wc-table th:not(:first-child),' +
- '#sdd-listing .wc-table td:not(:first-child) {' +
- ' text-align: right;' +
- '}',
- '.sdd-axisLabel{' +
- 'font-size:75%;' +
- 'border-radius:0.25em;' +
- 'padding:.2em .6em .3em;' +
- 'margin-right:0.4em;' +
- 'background-color:#5bc0de;' +
- 'color:white;' +
- 'font-weight:700;' +
- '}'
- ];
- var style = document.createElement('style');
- style.type = 'text/css';
- style.innerHTML = styles.join('\n');
- document.getElementsByTagName('head')[0].appendChild(style);
- }
-
- function safetyDeltaDelta() {
- var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'body';
- var settings = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
-
- //layout and styles
- layout(element);
- styles();
-
- //Define chart.
- var mergedSettings = Object.assign(
- {},
- JSON.parse(JSON.stringify(configuration.settings)),
- settings
- );
- var syncedSettings = configuration.syncSettings(mergedSettings);
- var syncedControlInputs = configuration.syncControlInputs(
- configuration.controlInputs(),
- syncedSettings
- );
- var controls = webcharts.createControls(
- document.querySelector(element).querySelector('#sdd-controls'),
- {
- location: 'top',
- inputs: syncedControlInputs
- }
- );
- var chart = webcharts.createChart(
- document.querySelector(element).querySelector('#sdd-chart'),
- syncedSettings,
- controls
- );
-
- //Define chart callbacks.
- for (var callback in callbacks) {
- chart.on(callback.substring(2).toLowerCase(), callbacks[callback]);
- } //listing
- var listing = webcharts.createTable(
- document.querySelector(element).querySelector('#sdd-listing'),
- configuration.listingSettings()
- );
- listing.wrap.style('display', 'none'); // empty table's popping up briefly
- listing.init([]);
- chart.listing = listing;
- listing.chart = chart;
-
- return chart;
- }
-
- return safetyDeltaDelta;
-});
\ No newline at end of file
diff --git a/inst/htmlwidgets/lib/safety-histogram-2.4.1/safetyHistogram.js b/inst/htmlwidgets/lib/safety-histogram-2.4.1/safetyHistogram.js
deleted file mode 100644
index c9d6667b..00000000
--- a/inst/htmlwidgets/lib/safety-histogram-2.4.1/safetyHistogram.js
+++ /dev/null
@@ -1,2205 +0,0 @@
-(function(global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined'
- ? (module.exports = factory(require('d3'), require('webcharts')))
- : typeof define === 'function' && define.amd
- ? define(['d3', 'webcharts'], factory)
- : (global.safetyHistogram = factory(global.d3, global.webCharts));
-})(this, function(d3, webcharts) {
- 'use strict';
-
- if (typeof Object.assign != 'function') {
- Object.defineProperty(Object, 'assign', {
- value: function assign(target, varArgs) {
- if (target == null) {
- // TypeError if undefined or null
- throw new TypeError('Cannot convert undefined or null to object');
- }
-
- var to = Object(target);
-
- for (var index = 1; index < arguments.length; index++) {
- var nextSource = arguments[index];
-
- if (nextSource != null) {
- // Skip over if undefined or null
- for (var nextKey in nextSource) {
- // Avoid bugs when hasOwnProperty is shadowed
- if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
- to[nextKey] = nextSource[nextKey];
- }
- }
- }
- }
-
- return to;
- },
- writable: true,
- configurable: true
- });
- }
-
- if (!Array.prototype.find) {
- Object.defineProperty(Array.prototype, 'find', {
- value: function value(predicate) {
- // 1. Let O be ? ToObject(this value).
- if (this == null) {
- throw new TypeError('"this" is null or not defined');
- }
-
- var o = Object(this);
-
- // 2. Let len be ? ToLength(? Get(O, 'length')).
- var len = o.length >>> 0;
-
- // 3. If IsCallable(predicate) is false, throw a TypeError exception.
- if (typeof predicate !== 'function') {
- throw new TypeError('predicate must be a function');
- }
-
- // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
- var thisArg = arguments[1];
-
- // 5. Let k be 0.
- var k = 0;
-
- // 6. Repeat, while k < len
- while (k < len) {
- // a. Let Pk be ! ToString(k).
- // b. Let kValue be ? Get(O, Pk).
- // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)).
- // d. If testResult is true, return kValue.
- var kValue = o[k];
- if (predicate.call(thisArg, kValue, k, o)) {
- return kValue;
- }
- // e. Increase k by 1.
- k++;
- }
-
- // 7. Return undefined.
- return undefined;
- }
- });
- }
-
- if (!Array.prototype.findIndex) {
- Object.defineProperty(Array.prototype, 'findIndex', {
- value: function value(predicate) {
- // 1. Let O be ? ToObject(this value).
- if (this == null) {
- throw new TypeError('"this" is null or not defined');
- }
-
- var o = Object(this);
-
- // 2. Let len be ? ToLength(? Get(O, "length")).
- var len = o.length >>> 0;
-
- // 3. If IsCallable(predicate) is false, throw a TypeError exception.
- if (typeof predicate !== 'function') {
- throw new TypeError('predicate must be a function');
- }
-
- // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
- var thisArg = arguments[1];
-
- // 5. Let k be 0.
- var k = 0;
-
- // 6. Repeat, while k < len
- while (k < len) {
- // a. Let Pk be ! ToString(k).
- // b. Let kValue be ? Get(O, Pk).
- // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)).
- // d. If testResult is true, return k.
- var kValue = o[k];
- if (predicate.call(thisArg, kValue, k, o)) {
- return k;
- }
- // e. Increase k by 1.
- k++;
- }
-
- // 7. Return -1.
- return -1;
- }
- });
- }
-
- Math.log10 = Math.log10 =
- Math.log10 ||
- function(x) {
- return Math.log(x) * Math.LOG10E;
- };
-
- // https://github.com/wbkd/d3-extended
- d3.selection.prototype.moveToFront = function() {
- return this.each(function() {
- this.parentNode.appendChild(this);
- });
- };
-
- d3.selection.prototype.moveToBack = function() {
- return this.each(function() {
- var firstChild = this.parentNode.firstChild;
- if (firstChild) {
- this.parentNode.insertBefore(this, firstChild);
- }
- });
- };
-
- function rendererSettings() {
- return {
- //required variables
- measure_col: 'TEST',
- value_col: 'STRESN',
-
- //optional variables
- id_col: 'USUBJID',
- unit_col: 'STRESU',
- normal_col_low: 'STNRLO',
- normal_col_high: 'STNRHI',
- filters: null,
- details: null,
-
- //miscellaneous settings
- start_value: null,
- normal_range: true,
- displayNormalRange: false,
- bin_algorithm: "Scott's normal reference rule",
- annotate_bin_boundaries: false
- };
- }
-
- function webchartsSettings() {
- return {
- x: {
- type: 'linear',
- column: null, // set in ./syncSettings
- label: null, // set in ../callbacks/onPreprocess/setXaxisLabel
- domain: [null, null], // set in ../callbacks/onPreprocess/setXdomain
- format: null, // set in ../callbacks/onPreprocess/calculateXPrecision
- bin: null // set in ../callbacks/onPreprocess/defineMeasureData
- },
- y: {
- type: 'linear',
- column: null,
- label: '# of Observations',
- domain: [0, null],
- format: '1d',
- behavior: 'flex'
- },
- marks: [
- {
- per: [], // set in ./syncSettings
- type: 'bar',
- summarizeX: 'mean',
- summarizeY: 'count',
- attributes: { 'fill-opacity': 0.75 }
- }
- ],
- aspect: 3
- };
- }
-
- function syncSettings(settings) {
- settings.x.column = settings.value_col;
- settings.x.bin_algorithm = settings.bin_algorithm;
- settings.marks[0].per[0] = settings.value_col;
-
- //update normal range settings if normal_range is set to false
- if (!settings.normal_range) {
- settings.normal_col_low = null;
- settings.normal_col_high = null;
- settings.displayNormalRange = false;
- }
-
- //handle a string argument to filters
- if (!(settings.filters instanceof Array))
- settings.filters = typeof settings.filters === 'string' ? [settings.filters] : [];
-
- //handle a string argument to details
- if (!(settings.details instanceof Array))
- settings.details = typeof settings.details === 'string' ? [settings.details] : [];
-
- //Define default details.
- var defaultDetails = [{ value_col: settings.id_col, label: 'Participant ID' }];
- if (Array.isArray(settings.filters))
- settings.filters
- .filter(function(filter) {
- return filter.value_col !== settings.id_col;
- })
- .forEach(function(filter) {
- return defaultDetails.push({
- value_col: filter.value_col ? filter.value_col : filter,
- label: filter.label
- ? filter.label
- : filter.value_col
- ? filter.value_col
- : filter
- });
- });
- defaultDetails.push({ value_col: settings.value_col, label: 'Result' });
- if (settings.normal_col_low)
- defaultDetails.push({
- value_col: settings.normal_col_low,
- label: 'Lower Limit of Normal'
- });
- if (settings.normal_col_high)
- defaultDetails.push({
- value_col: settings.normal_col_high,
- label: 'Upper Limit of Normal'
- });
-
- //If [settings.details] is not specified:
- if (!settings.details) settings.details = defaultDetails;
- else {
- //If [settings.details] is specified:
- //Allow user to specify an array of columns or an array of objects with a column property
- //and optionally a column label.
- settings.details.forEach(function(detail) {
- if (
- defaultDetails
- .map(function(d) {
- return d.value_col;
- })
- .indexOf(detail.value_col ? detail.value_col : detail) === -1
- )
- defaultDetails.push({
- value_col: detail.value_col ? detail.value_col : detail,
- label: detail.label
- ? detail.label
- : detail.value_col
- ? detail.value_col
- : detail
- });
- });
- settings.details = defaultDetails;
- }
-
- return settings;
- }
-
- function controlInputs() {
- return [
- {
- type: 'subsetter',
- value_col: 'sh_measure',
- label: 'Measure',
- start: null // set in ../callbacks/onInit/checkControls/updateMeasureFilter
- },
- {
- type: 'number',
- option: 'x.domain[0]',
- label: 'Lower',
- require: true
- },
- {
- type: 'number',
- option: 'x.domain[1]',
- label: 'Upper',
- require: true
- },
- {
- type: 'dropdown',
- option: 'x.bin_algorithm',
- label: 'Algorithm',
- values: [
- 'Square-root choice',
- "Sturges' formula",
- 'Rice Rule',
- //'Doane\'s formula',
- "Scott's normal reference rule",
- "Freedman-Diaconis' choice",
- "Shimazaki and Shinomoto's choice",
- 'Custom'
- ],
- require: true
- },
- {
- type: 'number',
- option: 'x.bin',
- label: 'Quantity'
- },
- {
- type: 'number',
- option: 'x.bin_width',
- label: 'Width'
- },
- {
- type: 'checkbox',
- option: 'displayNormalRange',
- label: 'Normal Range'
- },
- {
- type: 'radio',
- option: 'annotate_bin_boundaries',
- label: 'X-axis Ticks',
- values: [false, true],
- relabels: ['linear', 'bin boundaries']
- }
- ];
- }
-
- function syncControlInputs(controlInputs, settings) {
- //Add filters to default controls.
- if (Array.isArray(settings.filters) && settings.filters.length > 0) {
- var position = controlInputs.findIndex(function(input) {
- return input.label === 'Algorithm';
- });
- settings.filters.forEach(function(filter) {
- var filterObj = {
- type: 'subsetter',
- value_col: filter.value_col || filter,
- label: filter.label || filter.value_col || filter
- };
- controlInputs.splice(position, 0, filterObj);
- ++position;
- });
- }
-
- //Remove normal range control.
- if (!settings.normal_range)
- controlInputs.splice(
- controlInputs.findIndex(function(input) {
- return input.label === 'Normal Range';
- }),
- 1
- );
-
- return controlInputs;
- }
-
- var configuration = {
- rendererSettings: rendererSettings,
- webchartsSettings: webchartsSettings,
- settings: Object.assign({}, rendererSettings(), webchartsSettings()),
- syncSettings: syncSettings,
- controlInputs: controlInputs,
- syncControlInputs: syncControlInputs
- };
-
- var properties = {
- measure_col: {
- title: 'Medical Sign',
- description: 'a variable that contains the names of each medical sign',
- type: 'string',
- default: 'TEST',
- 'data-mapping': true,
- 'data-type': 'character',
- required: true
- },
- value_col: {
- title: 'Result',
- description:
- 'a variable that contains the results for each medical sign; non-numeric results are removed with a notification thrown to the log',
- type: 'string',
- default: 'STRESN',
- 'data-mapping': true,
- 'data-type': 'numeric',
- required: true
- },
- id_col: {
- title: 'ID',
- description: 'a variable that contains IDs for each participant',
- type: 'string',
- default: 'USUBJID',
- 'data-mapping': true,
- 'data-type': 'character',
- required: false
- },
- unit_col: {
- title: 'Unit',
- description: 'a variable that contains the units of each medical sign',
- type: 'string',
- default: 'STRESU',
- 'data-mapping': true,
- 'data-type': 'character',
- required: false
- },
- normal_col_low: {
- title: 'Lower Limit of Normal',
- description: 'a variable that contains the lower limit of normal of the medical sign',
- type: 'string',
- default: 'STNRLO',
- 'data-mapping': true,
- 'data-type': 'numeric',
- required: false
- },
- normal_col_high: {
- title: 'Upper Limit of Normal',
- description: 'a variable that contains the upper limit of normal of the medical sign',
- type: 'string',
- default: 'STNRHI',
- 'data-mapping': true,
- 'data-type': 'numeric',
- required: false
- },
- filters: {
- title: 'Filter Variables',
- description:
- 'an array of variables and metadata that will appear in the controls as data filters',
- type: 'array',
- items: {
- properties: {
- label: {
- description: 'a description of the variable',
- title: 'Variable Label',
- type: 'string'
- },
- value_col: {
- description: 'the name of the variable',
- title: 'Variable Name',
- type: 'string'
- }
- },
- type: 'object'
- },
- 'data-mapping': true,
- 'data-type': 'either',
- required: false
- },
- details: {
- title: 'Listing Variables',
- description: 'an array of variables and metadata that will appear in the data listing',
- type: 'array',
- items: {
- properties: {
- label: {
- description: 'a description of the variable',
- title: 'Variable Label',
- type: 'string'
- },
- value_col: {
- description: 'the name of the variable',
- title: 'Variable Name',
- type: 'string'
- }
- },
- type: 'object'
- },
- 'data-mapping': true,
- 'data-type': 'either',
- required: false
- },
- start_value: {
- title: 'Initial Medical Sign',
- description:
- 'the name of the initially displayed medical sign; defaults to the first measure in the data',
- type: 'string'
- },
- normal_range: {
- title: 'Generate Normal Range Control?',
- description:
- 'a boolean that dictates whether the normal range control will be generated',
- type: 'boolean',
- default: true
- },
- displayNormalRange: {
- title: 'Display Normal Range?',
- description:
- 'a boolean that dictates whether the normal range will be displayed initially',
- type: 'boolean',
- default: false
- }
- };
-
- function checkRequired() {
- var _this = this;
-
- this.variables.required = this.variables.definitions.filter(function(definition) {
- return definition.required === true;
- });
- this.variables.required.forEach(function(definition) {
- if (_this.variables.actual.indexOf(definition.setting) < 0) {
- definition.missing = true;
-
- //Define error text.
- var codeStyle = [
- 'padding: 1px 5px',
- 'white-space: prewrap',
- 'font-family: Consolas,Lucida Console,Courier New,monospace,sans-serif',
- 'background-color: #eff0f1'
- ];
- var errorText =
- "The variable specified for " +
- definition.property +
- ', ' +
- definition.setting +
- ', does not exist in the data.';
-
- //Print error to console.
- console.error(errorText.replace(/<.+?>/g, ''));
-
- //Print error to containing element.
- var div = d3.select(_this.div);
- div.append('p')
- .html(errorText)
- .style('color', 'red');
- }
- });
-
- //Destroy chart.
- if (
- this.variables.required.some(function(definition) {
- return definition.missing;
- })
- )
- this.destroy();
- }
-
- function checkOptional() {
- var _this = this;
-
- this.variables.optional = this.variables.definitions.filter(function(definition) {
- return definition.required === false;
- });
-
- this.variables.optional.forEach(function(definition) {
- if (definition.type === 'string') {
- if (_this.variables.actual.indexOf(definition.setting) < 0) {
- definition.missing = true;
- console.warn(
- 'The variable specified for [ ' +
- definition.property +
- ' ], ' +
- definition.setting +
- ', does not exist in the data.'
- );
- }
- } // standard data mappings
- else if (
- definition.type === 'array' &&
- Array.isArray(definition.setting) &&
- definition.setting.length
- ) {
- definition.setting.forEach(function(subDefinition, i) {
- var variable = subDefinition.value_col || subDefinition;
- if (_this.variables.actual.indexOf(variable) < 0) {
- definition.missing = true;
- console.warn(
- 'The variable specified for [ ' +
- definition.property +
- '[' +
- i +
- '] ], ' +
- variable +
- ', does not exist in the data.'
- );
- }
- });
- } // optional variable arrays (filters, listing columns)
-
- //Remove participant ID column from listing if variable is missing.
- if (definition.property === 'id_col' && definition.missing) {
- var index = _this.listing.config.cols.findIndex(function(col) {
- return col === definition.setting;
- });
- _this.listing.config.cols.splice(index, 1);
- _this.listing.config.headers.splice(index, 1);
- }
- });
- }
-
- function checkVariables() {
- var _this = this;
-
- this.variables = {
- actual: Object.keys(this.raw_data[0]),
- definitions: Object.keys(properties)
- .map(function(property) {
- var definition = properties[property];
- definition.property = property;
- definition.setting = _this.config[property];
- return definition;
- })
- .filter(function(definition) {
- return definition['data-mapping'];
- })
- };
- checkRequired.call(this);
- checkOptional.call(this);
- }
-
- function countParticipants() {
- var _this = this;
-
- this.participantCount = {
- N: d3
- .set(
- this.raw_data.map(function(d) {
- return d[_this.config.id_col];
- })
- )
- .values()
- .filter(function(value) {
- return !/^\s*$/.test(value);
- }).length,
- container: null, // set in ../onLayout/addParticipantCountContainer
- n: null, // set in ../onDraw/updateParticipantCount
- percentage: null // set in ../onDraw/updateParticipantCount
- };
- }
-
- function removeMissingResults() {
- var _this = this;
-
- //Split data into records with missing and nonmissing results.
- var missingResults = [];
- var nonMissingResults = [];
- this.raw_data.forEach(function(d) {
- if (/^\s*$/.test(d[_this.config.value_col])) missingResults.push(d);
- else nonMissingResults.push(d);
- });
-
- //Nest missing and nonmissing results by participant.
- var participantsWithMissingResults = d3
- .nest()
- .key(function(d) {
- return d[_this.config.id_col];
- })
- .rollup(function(d) {
- return d.length;
- })
- .entries(missingResults);
- var participantsWithNonMissingResults = d3
- .nest()
- .key(function(d) {
- return d[_this.config.id_col];
- })
- .rollup(function(d) {
- return d.length;
- })
- .entries(nonMissingResults);
-
- //Identify placeholder records, i.e. participants with a single missing result.
- this.removedRecords.placeholderRecords = participantsWithMissingResults
- .filter(function(d) {
- return (
- participantsWithNonMissingResults
- .map(function(d) {
- return d.key;
- })
- .indexOf(d.key) < 0 && d.values === 1
- );
- })
- .map(function(d) {
- return d.key;
- });
- if (this.removedRecords.placeholderRecords.length)
- console.log(
- this.removedRecords.placeholderRecords.length +
- ' participants without results have been detected.'
- );
-
- //Count the number of records with missing results.
- this.removedRecords.missing = d3.sum(
- participantsWithMissingResults.filter(function(d) {
- return _this.removedRecords.placeholderRecords.indexOf(d.key) < 0;
- }),
- function(d) {
- return d.values;
- }
- );
- if (this.removedRecords.missing > 0)
- console.warn(
- this.removedRecords.missing +
- ' record' +
- (this.removedRecords.missing > 1
- ? 's with a missing result have'
- : ' with a missing result has') +
- ' been removed.'
- );
-
- //Update data.
- this.raw_data = nonMissingResults;
- }
-
- function removeNonNumericResults() {
- var _this = this;
-
- //Filter out non-numeric results.
- var numericResults = this.raw_data.filter(function(d) {
- return /^-?[0-9.]+$/.test(d[_this.config.value_col]);
- });
- this.removedRecords.nonNumeric = this.raw_data.length - numericResults.length;
- if (this.removedRecords.nonNumeric > 0)
- console.warn(
- this.removedRecords.nonNumeric +
- ' record' +
- (this.removedRecords.nonNumeric > 1
- ? 's with a non-numeric result have'
- : ' with a non-numeric result has') +
- ' been removed.'
- );
-
- //Update data.
- this.raw_data = numericResults;
- }
-
- function cleanData() {
- this.removedRecords = {
- placeholderParticipants: null, // defined in './cleanData/removeMissingResults
- missing: null, // defined in './cleanData/removeMissingResults
- nonNumeric: null, // defined in './cleanData/removeNonNumericResults
- container: null // defined in ../onLayout/addRemovedRecordsContainer
- };
- removeMissingResults.call(this);
- removeNonNumericResults.call(this);
- this.initial_data = this.raw_data;
- }
-
- function addVariables() {
- var _this = this;
-
- this.raw_data.forEach(function(d) {
- //Concatenate unit to measure if provided.
- d[_this.config.measure_col] = d[_this.config.measure_col].trim();
- d.sh_measure = d.hasOwnProperty(_this.config.unit_col)
- ? d[_this.config.measure_col] + ' (' + d[_this.config.unit_col] + ')'
- : d[_this.config.measure_col];
- });
- }
-
- function participant() {
- var _this = this;
-
- this.participants = d3
- .set(
- this.initial_data.map(function(d) {
- return d[_this.config.id_col];
- })
- )
- .values()
- .sort();
- }
-
- function measure() {
- var _this = this;
-
- this.measures = d3
- .set(
- this.initial_data.map(function(d) {
- return d[_this.config.measure_col];
- })
- )
- .values()
- .sort();
- this.sh_measures = d3
- .set(
- this.initial_data.map(function(d) {
- return d.sh_measure;
- })
- )
- .values()
- .sort();
- }
-
- function defineSets() {
- participant.call(this);
- measure.call(this);
- }
-
- function updateMeasureFilter() {
- this.measure = {};
- var measureInput = this.controls.config.inputs.find(function(input) {
- return input.label === 'Measure';
- });
- if (
- this.config.start_value &&
- this.sh_measures.indexOf(this.config.start_value) < 0 &&
- this.measures.indexOf(this.config.start_value) < 0
- ) {
- measureInput.start = this.sh_measures[0];
- console.warn(
- this.config.start_value +
- ' is an invalid measure. Defaulting to ' +
- measureInput.start +
- '.'
- );
- } else if (
- this.config.start_value &&
- this.sh_measures.indexOf(this.config.start_value) < 0
- ) {
- measureInput.start = this.sh_measures[this.measures.indexOf(this.config.start_value)];
- console.warn(
- this.config.start_value +
- ' is missing the units value. Defaulting to ' +
- measureInput.start +
- '.'
- );
- } else measureInput.start = this.config.start_value || this.sh_measures[0];
- }
-
- function removeFilters() {
- var _this = this;
-
- this.controls.config.inputs = this.controls.config.inputs.filter(function(input) {
- if (input.type !== 'subsetter' || input.value_col === 'sh_measure') {
- return true;
- } else if (!_this.raw_data[0].hasOwnProperty(input.value_col)) {
- console.warn(
- 'The [ ' +
- input.label +
- ' ] filter has been removed because the variable does not exist.'
- );
- } else {
- var levels = d3
- .set(
- _this.raw_data.map(function(d) {
- return d[input.value_col];
- })
- )
- .values();
-
- if (levels.length === 1)
- console.warn(
- 'The [ ' +
- input.label +
- ' ] filter has been removed because the variable has only one level.'
- );
-
- return levels.length > 1;
- }
- });
- }
-
- function checkControls() {
- updateMeasureFilter.call(this);
- removeFilters.call(this);
- }
-
- function onInit() {
- // 0. Check variables.
- checkVariables.call(this);
-
- // 1. Count total participants prior to data cleaning.
- countParticipants.call(this);
-
- // 2. Drop missing values and remove measures with any non-numeric results.
- cleanData.call(this);
-
- // 3. Define additional variables.
- addVariables.call(this);
-
- // 4. Define sets.
- defineSets.call(this);
-
- // 5. Check controls.
- checkControls.call(this);
- }
-
- function identifyControls() {
- var context = this;
-
- var controlGroups = this.controls.wrap
- .style('padding-bottom', '8px')
- .selectAll('.control-group');
-
- //Give each control a unique ID.
- controlGroups
- .attr('id', function(d) {
- return d.label.toLowerCase().replace(/ /g, '-');
- })
- .each(function(d) {
- var controlGroup = d3.select(this);
- controlGroup.classed(d.type, true);
- context.controls[d.label] = controlGroup;
- });
-
- //Give x-axis controls a common class name.
- controlGroups
- .filter(function(d) {
- return ['x.domain[0]', 'x.domain[1]'].indexOf(d.option) > -1;
- })
- .classed('x-axis', true);
-
- //Give binning controls a common class name.
- controlGroups
- .filter(function(d) {
- return ['x.bin_algorithm', 'x.bin', 'x.bin_width'].indexOf(d.option) > -1;
- })
- .classed('bin', true);
- }
-
- function addXdomainResetButton() {
- var _this = this;
-
- //Add x-domain reset button container.
- this.controls.reset = {
- container: this.controls.wrap
- .insert('div', '#lower')
- .classed('control-group x-axis', true)
- .datum({
- type: 'button',
- option: 'x.domain',
- label: ''
- })
- .style('vertical-align', 'bottom')
- };
-
- //Add label.
- this.controls.reset.label = this.controls.reset.container
- .append('span')
- .attr('class', 'wc-control-label')
- .text('');
-
- //Add button.
- this.controls.reset.button = this.controls.reset.container
- .append('button')
- .text(' Reset ')
- .style('padding', '0px 5px')
- .on('click', function() {
- _this.config.x.domain = _this.measure.raw.domain;
-
- _this.controls.wrap
- .selectAll('.control-group')
- .filter(function(f) {
- return f.option === 'x.domain[0]';
- })
- .select('input')
- .property('value', _this.config.x.domain[0]);
-
- _this.controls.wrap
- .selectAll('.control-group')
- .filter(function(f) {
- return f.option === 'x.domain[1]';
- })
- .select('input')
- .property('value', _this.config.x.domain[1]);
-
- _this.draw();
- });
- }
-
- function insertGrouping(selector, label) {
- var className = label.toLowerCase().replace(/ /g, '-') + '-grouping';
- var div = this.controls.wrap
- .insert('div', selector)
- .classed(className + '-div', true)
- .style({
- display: 'inline-block',
- 'margin-right': '5px'
- });
- var fieldset = div
- .append('fieldset')
- .classed(className + '-fieldset', true)
- .style('padding', '0px 2px');
- var legend = fieldset
- .append('legend')
- .classed(className + '-legend', true)
- .text(label);
- this.controls.wrap.selectAll(selector).each(function(d) {
- this.style.marginTop = '0px';
- this.style.marginRight = '2px';
- this.style.marginBottom = '2px';
- this.style.marginLeft = '2px';
- fieldset.node().appendChild(this);
- });
- }
-
- function groupControls() {
- //Group x-axis controls.
- insertGrouping.call(this, '.x-axis', 'X-axis Limits');
-
- //Group filters.
- if (this.filters.length > 1)
- insertGrouping.call(this, '.subsetter:not(#measure)', 'Filters');
-
- //Group bin controls.
- insertGrouping.call(this, '.bin', 'Bins');
- }
-
- function addXdomainZoomButton() {
- var _this = this;
-
- if (
- this.filters.find(function(filter) {
- return filter.col !== 'sh_measure';
- })
- ) {
- //Add x-domain zoom button container.
- var resetContainer = this.controls.wrap
- .select('.x-axis-limits-grouping-fieldset')
- .append('div')
- .classed('control-group x-axis', true)
- .datum({
- type: 'button',
- option: 'x.domain',
- label: ''
- })
- .attr('title', 'Zoom in on filtered histogram.')
- .style({
- 'vertical-align': 'bottom',
- 'margin-top': '0px',
- 'margin-right': '2px',
- 'margin-bottom': '2px',
- 'margin-left': '2px'
- });
-
- //Add label.
- resetContainer
- .append('span')
- .attr('class', 'wc-control-label')
- .text('');
-
- //Add button.
- resetContainer
- .append('button')
- .text(' Zoom ')
- .style('padding', '0px 5px')
- .on('click', function() {
- _this.config.x.domain = _this.measure.filtered.domain;
-
- _this.controls.wrap
- .selectAll('.control-group')
- .filter(function(f) {
- return f.option === 'x.domain[0]';
- })
- .select('input')
- .property('value', _this.config.x.domain[0]);
-
- _this.controls.wrap
- .selectAll('.control-group')
- .filter(function(f) {
- return f.option === 'x.domain[1]';
- })
- .select('input')
- .property('value', _this.config.x.domain[1]);
-
- _this.draw();
- });
- }
- }
-
- function customizeBinsEventListener() {
- var _this = this;
-
- var context = this;
-
- this.controls.Algorithm.selectAll('.wc-control-label')
- .append('span')
- .classed('algorithm-explanation', true)
- .html(' ⓘ')
- .style('cursor', 'pointer')
- .on('click', function() {
- if (_this.config.x.bin_algorithm !== 'Custom')
- window.open(
- 'https://en.wikipedia.org/wiki/Histogram#' +
- _this.config.x.bin_algorithm
- .replace(/ /g, '_')
- .replace('Freedman-Diaconis', 'Freedman%E2%80%93Diaconis')
- );
- });
-
- this.controls.Quantity.selectAll('input')
- .attr({
- min: 1,
- step: 1
- })
- .on('change', function(d) {
- if (this.value < 1) this.value = 1;
- if (this.value % 1) this.value = Math.round(this.value);
- context.config.x.bin = this.value;
- context.config.x.bin_algorithm = 'Custom';
- context.controls.Algorithm.selectAll('option').property('selected', function(di) {
- return di === 'Custom';
- });
- context.draw();
- });
-
- this.controls.Width.selectAll('input').property('disabled', true);
- }
-
- function addParticipantCountContainer() {
- this.participantCount.container = this.controls.wrap
- .style('position', 'relative')
- .append('div')
- .attr('id', 'participant-count')
- .style({
- position: 'absolute',
- 'font-style': 'italic',
- bottom: '-10px',
- left: 0,
- display: this.variables.optional.find(function(definition) {
- return definition.property === 'id_col';
- }).missing
- ? 'none'
- : 'block'
- });
- }
-
- function addRemovedRecordsNote() {
- var _this = this;
-
- if (this.removedRecords.missing > 0 || this.removedRecords.nonNumeric > 0) {
- var message =
- this.removedRecords.missing > 0 && this.removedRecords.nonNumeric > 0
- ? this.removedRecords.missing +
- ' record' +
- (this.removedRecords.missing > 1 ? 's' : '') +
- ' with a missing result and ' +
- this.removedRecords.nonNumeric +
- ' record' +
- (this.removedRecords.nonNumeric > 1 ? 's' : '') +
- ' with a non-numeric result were removed.'
- : this.removedRecords.missing > 0
- ? this.removedRecords.missing +
- ' record' +
- (this.removedRecords.missing > 1 ? 's' : '') +
- ' with a missing result ' +
- (this.removedRecords.missing > 1 ? 'were' : 'was') +
- ' removed.'
- : this.removedRecords.nonNumeric > 0
- ? this.removedRecords.nonNumeric +
- ' record' +
- (this.removedRecords.nonNumeric > 1 ? 's' : '') +
- ' with a non-numeric result ' +
- (this.removedRecords.nonNumeric > 1 ? 'were' : 'was') +
- ' removed.'
- : '';
- this.removedRecords.container = this.controls.wrap
- .append('div')
- .style({
- position: 'absolute',
- 'font-style': 'italic',
- bottom: '-10px',
- right: 0
- })
- .text(message);
- this.removedRecords.container
- .append('span')
- .style({
- color: 'blue',
- 'text-decoration': 'underline',
- 'font-style': 'normal',
- 'font-weight': 'bold',
- cursor: 'pointer',
- 'font-size': '16px',
- 'margin-left': '5px'
- })
- .html('x')
- .on('click', function() {
- return _this.removedRecords.container.style('display', 'none');
- });
- }
- }
-
- function addBorderAboveChart() {
- this.wrap.style({
- 'border-top': '1px solid #ccc'
- });
- }
-
- function addFootnoteContainer() {
- this.footnotes = {
- container: this.wrap
- .insert('div', '.wc-chart')
- .classed('footnotes', true)
- .style({
- 'border-top': '1px solid #ccc',
- 'padding-top': '10px'
- })
- };
- this.footnotes.barClick = this.footnotes.container
- .append('p')
- .classed('footnote footnote--bar-click', true)
- .text('Click a bar for details.');
- this.footnotes.barDetails = this.footnotes.container
- .append('p')
- .classed('footnote footnote--bar-details', true);
- }
-
- function onLayout() {
- identifyControls.call(this);
- addXdomainResetButton.call(this);
- groupControls.call(this);
- addXdomainZoomButton.call(this);
- customizeBinsEventListener.call(this);
- addParticipantCountContainer.call(this);
- addRemovedRecordsNote.call(this);
- addBorderAboveChart.call(this);
- addFootnoteContainer.call(this);
- }
-
- function getCurrentMeasure() {
- this.measure.previous = this.measure.current;
- this.measure.current = this.controls.wrap.selectAll('#measure option:checked').text();
- this.config.x.label = this.measure.current;
- if (this.measure.current !== this.measure.previous) this.config.x.custom_bin = false;
- }
-
- function defineMeasureData() {
- var _this = this;
-
- //Filter data on selected measure.
- this.measure.raw = {
- data: this.initial_data.filter(function(d) {
- return d.sh_measure === _this.measure.current;
- })
- };
-
- //Apply other filters to measure data.
- this.measure.filtered = {
- data: this.measure.raw.data
- };
- this.filters.forEach(function(filter) {
- _this.measure.filtered.data = _this.measure.filtered.data.filter(function(d) {
- return filter.val === 'All'
- ? true
- : Array.isArray(filter.val)
- ? filter.val.includes(d[filter.col])
- : filter.val === d[filter.col];
- });
- });
-
- //Filter results on current x-domain.
- if (this.measure.current !== this.measure.previous)
- this.config.x.domain = d3.extent(
- this.measure.raw.data.map(function(d) {
- return +d[_this.config.value_col];
- })
- );
- this.measure.custom = {
- data: this.measure.raw.data.filter(function(d) {
- return (
- _this.config.x.domain[0] <= +d[_this.config.value_col] &&
- +d[_this.config.value_col] <= _this.config.x.domain[1]
- );
- })
- };
-
- //Define arrays of results, unique results, and extent of results.
- ['raw', 'custom', 'filtered'].forEach(function(property) {
- var obj = _this.measure[property];
-
- //Define array of all and unique results.
- obj.results = obj.data
- .map(function(d) {
- return +d[_this.config.value_col];
- })
- .sort(function(a, b) {
- return a - b;
- });
- obj.uniqueResults = d3.set(obj.results).values();
-
- //Calculate extent of data.
- obj.domain = property !== 'custom' ? d3.extent(obj.results) : _this.config.x.domain;
- });
- }
-
- function setXdomain() {
- if (this.measure.current !== this.measure.previous)
- this.config.x.domain = this.measure.raw.domain;
- else if (this.config.x.domain[0] > this.config.x.domain[1]) this.config.x.domain.reverse();
-
- //The x-domain can be in three states:
- //- the extent of all results
- //- user-defined, e.g. narrower to exclude outliers
- //
- //Bin width is calculated with two variables:
- //- the interquartile range
- //- the number of results
- //
- //1 When the x-domain is set to the extent of all results, the bin width should be calculated
- // with the unfiltered set of results, regardless of the state of the current filters.
- //
- //2 Given a user-defined x-domain, the bin width should be calculated with the results that
- // fall inside the current domain.
- this.measure.domain_state =
- (this.config.x.domain[0] === this.measure.raw.domain[0] &&
- this.config.x.domain[1] === this.measure.raw.domain[1]) ||
- this.measure.previous === undefined
- ? 'raw'
- : 'custom';
-
- //Set chart data to measure data.
- this.raw_data = this.measure[this.measure.domain_state].data.slice();
- }
-
- function calculateStatistics(obj) {
- var _this = this;
-
- ['raw', 'custom'].forEach(function(property) {
- var obj = _this.measure[property];
-
- //Calculate statistics.
- obj.stats = {
- n: obj.results.length,
- nUnique: obj.uniqueResults.length,
- min: obj.domain[0],
- q25: d3.quantile(obj.results, 0.25),
- median: d3.quantile(obj.results, 0.5),
- q75: d3.quantile(obj.results, 0.75),
- max: obj.domain[1],
- range: obj.domain[1] - obj.domain[0],
- std: d3.deviation(obj.results)
- };
- obj.stats.log10range = obj.stats.range > 0 ? Math.log10(obj.stats.range) : NaN;
- obj.stats.iqr = obj.stats.q75 - obj.stats.q25;
- });
- }
-
- function calculateSquareRootBinWidth(obj) {
- //https://en.wikipedia.org/wiki/Histogram#Square-root_choice
- var range = this.config.x.domain[1] - this.config.x.domain[0];
- obj.stats.SquareRootBins = Math.ceil(Math.sqrt(obj.stats.n));
- obj.stats.SquareRootBinWidth = range / obj.stats.SquareRootBins;
- }
-
- function calculateSturgesBinWidth(obj) {
- //https://en.wikipedia.org/wiki/Histogram#Sturges'_formula
- var range = this.config.x.domain[1] - this.config.x.domain[0];
- obj.stats.SturgesBins = Math.ceil(Math.log2(obj.stats.n)) + 1;
- obj.stats.SturgesBinWidth = range / obj.stats.SturgesBins;
- }
-
- function calculateRiceBinWidth(obj) {
- //https://en.wikipedia.org/wiki/Histogram#Rice_Rule
- var range = this.config.x.domain[1] - this.config.x.domain[0];
- obj.stats.RiceBins = Math.ceil(2 * Math.pow(obj.stats.n, 1.0 / 3.0));
- obj.stats.RiceBinWidth = range / obj.stats.RiceBins;
- }
-
- function calculateScottBinWidth(obj) {
- //https://en.wikipedia.org/wiki/Histogram#Scott's_normal_reference_rule
- var range = this.config.x.domain[1] - this.config.x.domain[0];
- obj.stats.ScottBinWidth = (3.5 * obj.stats.std) / Math.pow(obj.stats.n, 1.0 / 3.0);
- obj.stats.ScottBins =
- obj.stats.ScottBinWidth > 0
- ? Math.max(Math.ceil(range / obj.stats.ScottBinWidth), 5)
- : NaN;
- }
-
- function calculateFDBinWidth(obj) {
- //https://en.wikipedia.org/wiki/Histogram#Freedman%E2%80%93Diaconis'_choice
- var range = this.config.x.domain[1] - this.config.x.domain[0];
- obj.stats.FDBinWidth = (2 * obj.stats.iqr) / Math.pow(obj.stats.n, 1.0 / 3.0);
- obj.stats.FDBins =
- obj.stats.FDBinWidth > 0 ? Math.max(Math.ceil(range / obj.stats.FDBinWidth), 5) : NaN;
- }
-
- var toConsumableArray = function(arr) {
- if (Array.isArray(arr)) {
- for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
-
- return arr2;
- } else {
- return Array.from(arr);
- }
- };
-
- function calculateSSBinWidth(obj) {
- //https://en.wikipedia.org/wiki/Histogram#Shimazaki_and_Shinomoto's_choice
- var nBins = d3.range(2, 100); // number of bins
- var cost = d3.range(nBins.length); // cost function results
- var binWidths = [].concat(toConsumableArray(cost)); // bin widths
- var binBoundaries = [].concat(toConsumableArray(cost)); // bin boundaries
- var bins = [].concat(toConsumableArray(cost)); // bins
- var binSizes = [].concat(toConsumableArray(cost)); // bin lengths
- var meanBinSizes = [].concat(toConsumableArray(cost)); // mean of bin lengths
- var residuals = [].concat(toConsumableArray(cost)); // residuals
-
- var _loop = function _loop(i) {
- binWidths[i] = obj.stats.range / nBins[i];
- binBoundaries[i] = d3.range(obj.stats.min, obj.stats.max, obj.stats.range / nBins[i]);
- bins[i] = d3.layout.histogram().bins(nBins[i] - 1)(
- /*.bins(binBoundaries[i])*/ obj.results
- );
- binSizes[i] = bins[i].map(function(arr) {
- return arr.length;
- });
- meanBinSizes[i] = d3.mean(binSizes[i]);
- residuals[i] =
- d3.sum(
- binSizes[i].map(function(binSize) {
- return Math.pow(binSize - meanBinSizes[i], 2);
- })
- ) / nBins[i];
- cost[i] = (2 * meanBinSizes[i] - residuals[i]) / Math.pow(binWidths[i], 2);
- };
-
- for (var i = 0; i < nBins.length; i++) {
- _loop(i);
- }
-
- //consoleLogVars(
- // {
- // nBins,
- // binWidths,
- // binBoundaries,
- // //bins,
- // binSizes,
- // meanBinSizes,
- // residuals,
- // cost
- // },
- // 5
- //);
-
- var minCost = d3.min(cost);
- var idx = cost.findIndex(function(c) {
- return c === minCost;
- });
-
- obj.stats.SSBinWidth = binWidths[idx];
- obj.stats.SSBins = nBins[idx];
- //const optBinBoundaries = range(obj.stats.min, obj.stats.max, obj.stats.range/optNBins);
- }
-
- function calcualteBinWidth() {
- var _this = this;
-
- ['raw', 'custom'].forEach(function(property) {
- var obj = _this.measure[property];
-
- //Calculate bin width with the selected algorithm.
- switch (_this.config.x.bin_algorithm) {
- case 'Square-root choice':
- calculateSquareRootBinWidth.call(_this, obj);
- obj.stats.nBins =
- obj.stats.SquareRootBins < obj.stats.nUnique
- ? obj.stats.SquareRootBins
- : obj.stats.nUnique;
- break;
- case "Sturges' formula":
- calculateSturgesBinWidth.call(_this, obj);
- obj.stats.nBins =
- obj.stats.SturgesBins < obj.stats.nUnique
- ? obj.stats.SturgesBins
- : obj.stats.nUnique;
- break;
- case 'Rice Rule':
- calculateRiceBinWidth.call(_this, obj);
- obj.stats.nBins =
- obj.stats.RiceBins < obj.stats.nUnique
- ? obj.stats.RiceBins
- : obj.stats.nUnique;
- break;
- //case 'Doane\'s formula':
- // console.log(4);
- // calculateDoaneBinWidth.call(this, obj);
- // obj.stats.nBins =
- // obj.stats.DoaneBins < obj.stats.nUnique ? obj.stats.DoaneBins : obj.stats.nUnique;
- // break;
- case "Scott's normal reference rule":
- calculateScottBinWidth.call(_this, obj);
- obj.stats.nBins =
- obj.stats.ScottBins < obj.stats.nUnique
- ? obj.stats.ScottBins
- : obj.stats.nUnique;
- break;
- case "Freedman-Diaconis' choice":
- calculateFDBinWidth.call(_this, obj);
- obj.stats.nBins =
- obj.stats.FDBins < obj.stats.nUnique ? obj.stats.FDBins : obj.stats.nUnique;
- break;
- case "Shimazaki and Shinomoto's choice":
- calculateSSBinWidth.call(_this, obj);
- obj.stats.nBins =
- obj.stats.SSBins < obj.stats.nUnique ? obj.stats.SSBins : obj.stats.nUnique;
- break;
- default:
- //Handle custom number of bins.
- obj.stats.nBins = _this.config.x.bin;
- //obj.stats.binWidth = this.config.x.domain[1] - this.config.x.domain[0] / this.config.x.bin;
- }
-
- //Calculate bin width.
- obj.stats.binWidth = obj.stats.range / obj.stats.nBins;
- obj.stats.binBoundaries = d3.range(obj.stats.nBins).concat(obj.domain[1]);
- });
-
- //Update chart config and set chart data to measure data.
- this.config.x.bin = this.measure[this.measure.domain_state].stats.nBins;
- this.config.x.bin_width = this.measure[this.measure.domain_state].stats.binWidth;
- }
-
- function calculateXPrecision() {
- //define the precision of the x-axis
- this.config.x.precisionFactor = Math.round(
- this.measure[this.measure.domain_state].stats.log10range
- );
- this.config.x.precision = Math.pow(10, this.config.x.precisionFactor);
-
- //x-axis format
- this.config.x.format =
- this.config.x.precisionFactor > 0
- ? '.0f'
- : '.' + (Math.abs(this.config.x.precisionFactor) + 1) + 'f';
- this.config.x.d3format = d3.format(this.config.x.format);
-
- //one more precision please: bin format
- this.config.x.format1 =
- this.config.x.precisionFactor > 0
- ? '.1f'
- : '.' + (Math.abs(this.config.x.precisionFactor) + 2) + 'f';
- this.config.x.d3format1 = d3.format(this.config.x.format1);
-
- //define the size of the x-axis limit increments
- var step =
- this.measure[this.measure.domain_state].stats.range > 0
- ? Math.abs(this.measure[this.measure.domain_state].stats.range / 15) // non-zero range
- : this.measure[this.measure.domain_state].results[0] !== 0
- ? Math.abs(this.measure[this.measure.domain_state].results[0] / 15) // zero range, non-zero result(s)
- : 1; // zero range, zero result(s)
- if (step < 1) {
- var x10 = 0;
- do {
- step = step * 10;
- ++x10;
- } while (step < 1);
- step = Math.round(step) / Math.pow(10, x10);
- } else step = Math.round(step);
- this.measure.step = step || 1;
- }
-
- function updateXAxisResetButton() {
- //Update tooltip of x-axis domain reset button.
- if (this.measure.current !== this.measure.previous) {
- this.controls.reset.container.attr(
- 'title',
- 'Initial Limits: [' +
- this.config.x.d3format1(this.config.x.domain[0]) +
- ' - ' +
- this.config.x.d3format1(this.config.x.domain[1]) +
- ']'
- );
- }
- }
-
- function updateXAxisLimits() {
- this.controls.wrap
- .selectAll('#lower input')
- .attr('step', this.measure.step) // set in ./calculateXPrecision
- .style('box-shadow', 'none')
- .property('value', this.config.x.d3format1(this.config.x.domain[0]));
-
- this.controls.wrap
- .selectAll('#upper input')
- .attr('step', this.measure.step) // set in ./calculateXPrecision
- .style('box-shadow', 'none')
- .property('value', this.config.x.d3format1(this.config.x.domain[1]));
- }
-
- function updateBinAlogrithm() {
- this.controls.Algorithm.selectAll('.algorithm-explanation')
- .style('display', this.config.x.bin_algorithm !== 'Custom' ? null : 'none')
- .attr(
- 'title',
- this.config.x.bin_algorithm !== 'Custom'
- ? 'View information on ' + this.config.x.bin_algorithm
- : null
- );
- }
-
- function updateBinWidth() {
- this.controls.Width.selectAll('input').property(
- 'value',
- this.config.x.d3format1(this.config.x.bin_width)
- );
- }
-
- function updateBinQuantity() {
- this.controls.Quantity.selectAll('input').property('value', this.config.x.bin);
- }
-
- function updateControls() {
- updateXAxisResetButton.call(this);
- updateXAxisLimits.call(this);
- updateBinAlogrithm.call(this);
- updateBinWidth.call(this);
- updateBinQuantity.call(this);
- }
-
- function defineBinBoundaries() {
- var _this = this;
-
- var obj = this.measure[this.measure.domain_state];
- this.measure.binBoundaries = obj.stats.binBoundaries.map(function(d, i) {
- var value = obj.domain[0] + obj.stats.binWidth * i;
- return {
- value: value,
- value1: _this.config.x.d3format(value),
- value2: _this.config.x.d3format1(value)
- };
- });
- }
-
- function onPreprocess() {
- // 1. Capture currently selected measure - needed in 2a.
- getCurrentMeasure.call(this);
-
- // 2. Filter data on currently selected measure - needed in 3a and 3b.
- defineMeasureData.call(this);
-
- // 3a Set x-domain given currently selected measure - needed in 4a and 4b.
- setXdomain.call(this);
-
- // 3b Calculate statistics - needed in 4a and 4b.
- calculateStatistics.call(this);
-
- // 4a Define precision of measure - needed in step 5a and 5b.
- calculateXPrecision.call(this);
-
- // 4b Calculate bin width - needed in step 5c.
- calcualteBinWidth.call(this);
-
- // 5a Update x-axis and bin controls after.
- updateControls.call(this);
-
- // 5b Define bin boundaries given bin width and precision.
- defineBinBoundaries.call(this);
- }
-
- function onDatatransform() {}
-
- function updateParticipantCount() {
- var _this = this;
-
- //count the number of unique ids in the current chart and calculate the percentage
- this.participantCount.n = d3
- .set(
- this.filtered_data.map(function(d) {
- return d[_this.config.id_col];
- })
- )
- .values().length;
- this.participantCount.percentage = d3.format('0.1%')(
- this.participantCount.n / this.participantCount.N
- );
-
- //clear the annotation
- this.participantCount.container.selectAll('*').remove();
-
- //update the annotation
- this.participantCount.container.text(
- '\n' +
- this.participantCount.n +
- ' of ' +
- this.participantCount.N +
- ' participant(s) shown (' +
- this.participantCount.percentage +
- ')'
- );
- }
-
- function resetRenderer() {
- delete this.highlightedBin;
- delete this.highlighteD;
-
- //Remove bin boundaries.
- this.svg.select('g.bin-boundaries').remove();
-
- //Reset bar highlighting.
- this.svg
- .selectAll('.bar-group')
- .classed('selected', false)
- .selectAll('.bar')
- .attr('fill-opacity', 0.75);
-
- //Reset footnotes.
- this.footnotes.barClick
- .style({
- 'text-decoration': 'none',
- cursor: 'normal'
- })
- .text('Click a bar for details.');
- this.footnotes.barDetails.text('');
-
- //Reset listing.
- this.listing.draw([]);
- this.listing.wrap.style('display', 'none');
- }
-
- function increasePrecision() {
- var _this = this;
-
- var ticks = this.x.ticks().map(function(d) {
- return _this.config.x.d3format(d);
- });
- if (
- d3
- .nest()
- .key(function(d) {
- return d;
- })
- .rollup(function(d) {
- return d.length;
- })
- .entries(ticks)
- .some(function(d) {
- return d.values > 1;
- })
- )
- this.config.x.format = this.config.x.format1;
- }
-
- function onDraw() {
- updateParticipantCount.call(this);
- resetRenderer.call(this);
- increasePrecision.call(this);
- }
-
- function drawZeroRangeBar() {
- var _this = this;
-
- if (
- this.current_data.length === 1 &&
- this.measure.filtered.domain[0] === this.measure.filtered.domain[1]
- ) {
- var width = this.plot_width / 25;
- this.svg
- .selectAll('g.bar-group rect')
- .transition()
- .delay(250) // wait for initial marks to transition
- .attr({
- x: function x(d) {
- return _this.x(d.values.x) - width / 2;
- },
- width: width
- });
- }
- }
-
- function addHoverBars() {
- var context = this;
-
- var bins = this.svg.selectAll('.bar-group').each(function(d) {
- var g = d3.select(this);
- g.selectAll('.hover-bar').remove();
-
- //Drawing a path instead of a rect because Webcharts messes up the original rect on resize.
- var x = context.x(d.rangeLow);
- var y = 0;
- var width = context.x(d.rangeHigh) - context.x(d.rangeLow);
- var height = context.plot_height;
- var hoverBar = g
- .insert('path', ':first-child')
- .classed('hover-bar', true)
- .attr({
- d: 'M ' + x + ' ' + y + ' V ' + height + ' H ' + (x + width) + ' V ' + y,
- fill: 'black',
- 'fill-opacity': 0,
- stroke: 'black',
- 'stroke-opacity': 0
- });
- d.footnote =
- "" +
- d.values.raw.length +
- ' records with ' +
- (context.measure.current + " values ≥") +
- (context.config.x.d3format1(d.rangeLow) +
- ' and ' +
- (d.rangeHigh < context.config.x.domain[1] ? '<' : '≤') +
- "" +
- context.config.x.d3format1(d.rangeHigh) +
- '');
- });
- }
-
- function mouseout(element, d) {
- //Update footnote.
- this.footnotes.barDetails.html(
- this.highlightedBin ? 'Table displays ' + this.highlighteD.footnote + '.' : ''
- );
-
- //Remove bar highlight.
- var selection = d3.select(element);
- selection.selectAll('.bar').attr('stroke', this.colorScale());
- }
-
- function mouseover(element, d) {
- //Update bar details footnote.
- this.footnotes.barDetails.html('Bar encompasses ' + d.footnote + '.');
-
- //Highlight bar.
- var selection = d3.select(element);
- if (!/trident/i.test(navigator.userAgent)) selection.moveToFront();
- selection.selectAll('.bar').attr('stroke', 'black');
- }
-
- function select(element, d) {
- var _this = this;
-
- //Reduce bin opacity and highlight selected bin.
- this.svg
- .selectAll('.bar-group')
- .selectAll('.bar')
- .attr('fill-opacity', 0.5);
- d3.select(element)
- .select('.bar')
- .attr('fill-opacity', 1);
-
- //Update bar click footnote
- this.footnotes.barClick
- .style({
- cursor: 'pointer',
- 'text-decoration': 'underline'
- })
- .text('Click here to remove details and clear highlighting.')
- .on('click', function() {
- resetRenderer.call(_this);
- });
-
- //Update bar details footnote.
- this.footnotes.barDetails.html('Table displays ' + d.footnote + '.');
-
- //Draw listing.
- this.listing.draw(d.values.raw);
- this.listing.wrap.style('display', 'inline-block');
- }
-
- function deselect(element, d) {
- delete this.highlightedBin;
- delete this.highlighteD;
- this.listing.draw([]);
- this.listing.wrap.style('display', 'none');
- this.svg.selectAll('.bar').attr('fill-opacity', 0.75);
-
- this.footnotes.barClick
- .style({
- cursor: 'normal',
- 'text-decoration': 'none'
- })
- .text('Click a bar for details.');
- this.footnotes.barDetails.text(
- d.values.raw.length +
- ' records with ' +
- (this.measure.current + ' values from ') +
- (this.config.x.d3format1(d.rangeLow) +
- ' to ' +
- this.config.x.d3format1(d.rangeHigh))
- );
- }
-
- function click(element, d) {
- this.highlightedBin = d.key;
- this.highlighteD = d;
- var selection = d3.select(element);
- var selected = selection.classed('selected');
- this.svg.selectAll('.bar-group').classed('selected', false);
- selection.classed('selected', !selected);
-
- if (!selected) select.call(this, element, d);
- else deselect.call(this, element, d);
- }
-
- function addBinEventListeners() {
- var context = this;
-
- var barGroups = this.svg.selectAll('.bar-group').style('cursor', 'pointer');
-
- barGroups
- .on('mouseover', function(d) {
- mouseover.call(context, this, d);
- })
- .on('mouseout', function(d) {
- mouseout.call(context, this, d);
- })
- .on('click', function(d) {
- click.call(context, this, d);
- });
- }
-
- function drawNormalRanges() {
- var _this = this;
-
- this.controls.wrap.select('.normal-range-list').remove();
- this.svg.select('.normal-ranges').remove();
-
- if (this.config.displayNormalRange && this.filtered_data.length > 0) {
- //Capture distinct normal ranges in filtered data.
- var normalRanges = d3
- .nest()
- .key(function(d) {
- return d[_this.config.normal_col_low] + ',' + d[_this.config.normal_col_high];
- }) // set key to comma-delimited normal range
- .rollup(function(d) {
- return d.length;
- })
- .entries(this.filtered_data)
- .map(function(d) {
- d.keySplit = d.key.split(',');
-
- //lower
- d.lower = +d.keySplit[0];
- d.x1 = d.lower >= _this.x_dom[0] ? _this.x(d.lower) : 0;
-
- //upper
- d.upper = +d.keySplit[1];
- d.x2 = d.upper <= _this.x_dom[1] ? _this.x(d.upper) : _this.plot_width;
-
- //width
- d.width = d.x2 - d.x1;
-
- //tooltip
- d.rate = d.values / _this.filtered_data.length;
- d.tooltip =
- d.values < _this.filtered_data.length
- ? d.lower +
- ' - ' +
- d.upper +
- ' (' +
- d3.format('%')(d.rate) +
- ' of records)'
- : d.lower + ' - ' + d.upper;
-
- //plot if:
- // - at least one of the limits of normal fall within the current x-domain
- // - the lower limit is less than the current x-domain and the upper limit is greater than current the x-domain
- d.plot =
- (_this.x_dom[0] <= d.lower && d.lower <= _this.x_dom[1]) ||
- (_this.x_dom[0] <= d.upper && d.upper <= _this.x_dom[1]) ||
- (_this.x_dom[0] >= d.lower && d.upper >= _this.x_dom[1]);
-
- return d;
- })
- .sort(function(a, b) {
- var diff_lower = a.lower - b.lower;
- var diff_upper = a.upper - b.upper;
- return diff_lower ? diff_lower : diff_upper ? diff_upper : 0;
- }); // sort normal ranges so larger normal ranges plot beneath smaller normal ranges
-
- //Add tooltip to Normal Range control that lists normal ranges.
- this.controls.wrap
- .selectAll('#normal-range .wc-control-label')
- .append('span')
- .classed('normal-range-list', true)
- .html(' ⓘ')
- .attr(
- 'title',
- normalRanges.length > 1
- ? this.measure.current +
- ' normal ranges:\n' +
- normalRanges
- .map(function(normalRange) {
- return normalRange.tooltip;
- })
- .join('\n')
- : this.measure.current + ' normal range: ' + normalRanges[0].tooltip
- )
- .style('cursor', 'default');
-
- //Add groups in which to draw normal range rectangles and annotations.
- var group = this.svg.insert('g', '.bar-supergroup').classed('normal-ranges', true);
- var groups = group
- .selectAll('g.normal-range')
- .data(
- normalRanges.filter(function(d) {
- return d.plot;
- })
- )
- .enter()
- .append('g')
- .classed('normal-range', true);
-
- //Draw normal range rectangles.
- var rectangles = groups
- .append('rect')
- .classed('normal-range__rect', true)
- .attr({
- x: function x(d) {
- return d.x1;
- },
- y: 0,
- width: function width(d) {
- return d.width;
- },
- height: this.plot_height,
- stroke: '#c26683',
- fill: '#c26683',
- 'stroke-opacity': function strokeOpacity(d) {
- return (d.values / _this.filtered_data.length) * 0.5;
- },
- 'fill-opacity': function fillOpacity(d) {
- return (d.values / _this.filtered_data.length) * 0.25;
- }
- }); // opacity as a function of fraction of records with the given normal range
- }
- }
-
- function maintainBinHighlighting() {
- var _this = this;
-
- this.svg.selectAll('.bar').attr('fill-opacity', function(d) {
- return _this.highlightedBin
- ? d.key !== _this.highlightedBin
- ? 0.5
- : 1
- : _this.marks[0].attributes['fill-opacity'];
- });
- }
-
- function removeXAxisTicks() {
- if (this.config.annotate_bin_boundaries) this.svg.selectAll('.x.axis .tick').remove();
- }
-
- function annotateBinBoundaries() {
- var _this = this;
-
- if (this.config.annotate_bin_boundaries) {
- //Remove bin boundaries.
- this.svg.select('g.bin-boundaries').remove();
-
- //Check for repeats of values formatted with lower precision.
- var repeats = d3
- .nest()
- .key(function(d) {
- return d.value1;
- })
- .rollup(function(d) {
- return d.length;
- })
- .entries(this.measure.binBoundaries)
- .some(function(d) {
- return d.values > 1;
- });
-
- //Annotate bin boundaries.
- var axis = this.svg.append('g').classed('bin-boundaries axis', true);
- var ticks = axis
- .selectAll('g.bin-boundary')
- .data(this.measure.binBoundaries)
- .enter()
- .append('g')
- .classed('bin-boundary tick', true);
- var texts = ticks
- .append('text')
- .attr({
- x: function x(d) {
- return _this.x(d.value);
- },
- y: this.plot_height,
- dy: '16px',
- 'text-anchor': 'middle'
- })
- .text(function(d) {
- return repeats ? d.value2 : d.value1;
- });
-
- //Thin ticks.
- var textDimensions = [];
- texts.each(function(d) {
- var text = d3.select(this);
- var bbox = this.getBBox();
- if (
- textDimensions.some(function(textDimension) {
- return textDimension.x + textDimension.width > bbox.x - 5;
- })
- )
- text.remove();
- else
- textDimensions.push({
- x: bbox.x,
- width: bbox.width,
- y: bbox.y,
- height: bbox.height
- });
- });
- }
- }
-
- function onResize() {
- //Draw custom bin for single observation subsets.
- drawZeroRangeBar.call(this);
-
- //Add invisible bars for improved hovering.
- addHoverBars.call(this);
-
- //Display data listing on bin click.
- addBinEventListeners.call(this);
-
- //Visualize normal ranges.
- drawNormalRanges.call(this);
-
- //Keep highlighted bin highlighted on resize.
- maintainBinHighlighting.call(this);
-
- //Remove x-axis ticks.
- removeXAxisTicks.call(this);
-
- //Annotate bin boundaries.
- annotateBinBoundaries.call(this);
- }
-
- function onDestroy() {
- this.listing.destroy();
- d3.select(this.div)
- .selectAll('.loader')
- .remove();
- }
-
- var callbacks = {
- onInit: onInit,
- onLayout: onLayout,
- onPreprocess: onPreprocess,
- onDatatransform: onDatatransform,
- onDraw: onDraw,
- onResize: onResize,
- onDestroy: onDestroy
- };
-
- function safetyHistogram() {
- var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'body';
- var settings = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
-
- //Define chart.
- var mergedSettings = Object.assign(
- {},
- JSON.parse(JSON.stringify(configuration.settings)),
- settings
- );
- var syncedSettings = configuration.syncSettings(mergedSettings);
- var syncedControlInputs = configuration.syncControlInputs(
- configuration.controlInputs(),
- syncedSettings
- );
- var controls = webcharts.createControls(element, {
- location: 'top',
- inputs: syncedControlInputs
- });
- var chart = webcharts.createChart(element, syncedSettings, controls);
-
- //Define chart callbacks.
- for (var callback in callbacks) {
- chart.on(callback.substring(2).toLowerCase(), callbacks[callback]);
- } //Define listing
- var listingSettings = Object.assign(
- {},
- {
- cols: syncedSettings.details.map(function(detail) {
- return detail.value_col;
- }),
- headers: syncedSettings.details.map(function(detail) {
- return detail.label;
- })
- },
- syncedSettings
- );
- var listing = webcharts.createTable(element, listingSettings);
- listing.on('layout', function() {
- //Style table
- this.wrap.style('display', 'none');
- this.wrap.selectAll('.table-top,table,.table-bottom').style({
- float: 'left',
- clear: 'left',
- width: '100%'
- });
- this.table.style('white-space', 'nowrap');
- });
- //Attach listing to chart.
- chart.listing = listing;
- listing.chart = chart;
-
- //Initialize listing and hide initially.
-
- chart.listing.init([]);
-
- return chart;
- }
-
- return safetyHistogram;
-});
diff --git a/inst/htmlwidgets/lib/safety-outlier-explorer-2.6.0/safetyOutlierExplorer.js b/inst/htmlwidgets/lib/safety-outlier-explorer-2.6.0/safetyOutlierExplorer.js
deleted file mode 100644
index 0d17de1c..00000000
--- a/inst/htmlwidgets/lib/safety-outlier-explorer-2.6.0/safetyOutlierExplorer.js
+++ /dev/null
@@ -1,2478 +0,0 @@
-(function(global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined'
- ? (module.exports = factory(require('d3'), require('webcharts')))
- : typeof define === 'function' && define.amd
- ? define(['d3', 'webcharts'], factory)
- : ((global = global || self),
- (global.safetyOutlierExplorer = factory(global.d3, global.webCharts)));
-})(this, function(d3$1, webcharts) {
- 'use strict';
-
- if (typeof Object.assign != 'function') {
- Object.defineProperty(Object, 'assign', {
- value: function assign(target, varArgs) {
- if (target == null) {
- // TypeError if undefined or null
- throw new TypeError('Cannot convert undefined or null to object');
- }
-
- var to = Object(target);
-
- for (var index = 1; index < arguments.length; index++) {
- var nextSource = arguments[index];
-
- if (nextSource != null) {
- // Skip over if undefined or null
- for (var nextKey in nextSource) {
- // Avoid bugs when hasOwnProperty is shadowed
- if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
- to[nextKey] = nextSource[nextKey];
- }
- }
- }
- }
-
- return to;
- },
- writable: true,
- configurable: true
- });
- }
-
- if (!Array.prototype.find) {
- Object.defineProperty(Array.prototype, 'find', {
- value: function value(predicate) {
- // 1. Let O be ? ToObject(this value).
- if (this == null) {
- throw new TypeError('"this" is null or not defined');
- }
-
- var o = Object(this);
-
- // 2. Let len be ? ToLength(? Get(O, 'length')).
- var len = o.length >>> 0;
-
- // 3. If IsCallable(predicate) is false, throw a TypeError exception.
- if (typeof predicate !== 'function') {
- throw new TypeError('predicate must be a function');
- }
-
- // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
- var thisArg = arguments[1];
-
- // 5. Let k be 0.
- var k = 0;
-
- // 6. Repeat, while k < len
- while (k < len) {
- // a. Let Pk be ! ToString(k).
- // b. Let kValue be ? Get(O, Pk).
- // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)).
- // d. If testResult is true, return kValue.
- var kValue = o[k];
- if (predicate.call(thisArg, kValue, k, o)) {
- return kValue;
- }
- // e. Increase k by 1.
- k++;
- }
-
- // 7. Return undefined.
- return undefined;
- }
- });
- }
-
- if (!Array.prototype.findIndex) {
- Object.defineProperty(Array.prototype, 'findIndex', {
- value: function value(predicate) {
- // 1. Let O be ? ToObject(this value).
- if (this == null) {
- throw new TypeError('"this" is null or not defined');
- }
-
- var o = Object(this);
-
- // 2. Let len be ? ToLength(? Get(O, "length")).
- var len = o.length >>> 0;
-
- // 3. If IsCallable(predicate) is false, throw a TypeError exception.
- if (typeof predicate !== 'function') {
- throw new TypeError('predicate must be a function');
- }
-
- // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
- var thisArg = arguments[1];
-
- // 5. Let k be 0.
- var k = 0;
-
- // 6. Repeat, while k < len
- while (k < len) {
- // a. Let Pk be ! ToString(k).
- // b. Let kValue be ? Get(O, Pk).
- // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)).
- // d. If testResult is true, return k.
- var kValue = o[k];
- if (predicate.call(thisArg, kValue, k, o)) {
- return k;
- }
- // e. Increase k by 1.
- k++;
- }
-
- // 7. Return -1.
- return -1;
- }
- });
- }
-
- Math.log10 =
- Math.log10 ||
- function(x) {
- return Math.log(x) * Math.LOG10E;
- };
-
- (function() {
- if (typeof window.CustomEvent === 'function') return false;
-
- function CustomEvent(event, params) {
- params = params || { bubbles: false, cancelable: false, detail: null };
- var evt = document.createEvent('CustomEvent');
- evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
- return evt;
- }
-
- window.CustomEvent = CustomEvent;
- })();
-
- // https://github.com/wbkd/d3-extended
- d3$1.selection.prototype.moveToFront = function() {
- return this.each(function() {
- this.parentNode.appendChild(this);
- });
- };
-
- d3$1.selection.prototype.moveToBack = function() {
- return this.each(function() {
- var firstChild = this.parentNode.firstChild;
- if (firstChild) {
- this.parentNode.insertBefore(this, firstChild);
- }
- });
- };
-
- function rendererSettings() {
- return {
- //participant
- id_col: 'USUBJID',
- details: [
- { value_col: 'AGE', label: 'Age' },
- { value_col: 'SEX', label: 'Sex' },
- { value_col: 'RACE', label: 'Race' }
- ],
-
- //timing
- time_cols: [
- {
- type: 'ordinal',
- value_col: 'VISIT',
- label: 'Visit',
- order_col: 'VISITNUM',
- order: null,
- rotate_tick_labels: true,
- vertical_space: 100
- },
- {
- type: 'linear',
- value_col: 'DY',
- label: 'Study Day',
- order_col: 'DY',
- order: null,
- rotate_tick_labels: false,
- vertical_space: 0
- }
- ],
- visits_without_data: false,
- unscheduled_visits: false,
- unscheduled_visit_pattern: '/unscheduled|early termination/i',
- unscheduled_visit_values: null, // takes precedence over unscheduled_visit_pattern
-
- //measure
- measure_col: 'TEST',
- start_value: null,
- unit_col: 'STRESU',
-
- //result
- value_col: 'STRESN',
-
- //normal range
- normal_col_low: 'STNRLO',
- normal_col_high: 'STNRHI',
- normal_range_method: 'LLN-ULN',
- normal_range_sd: 1.96,
- normal_range_quantile_low: 0.05,
- normal_range_quantile_high: 0.95,
-
- //filters
- filters: null,
-
- //marks
- line_attributes: {
- stroke: 'black',
- 'stroke-width': 0.5,
- 'stroke-opacity': 0.75
- },
- point_attributes: {
- stroke: '#1f78b4',
- 'stroke-width': 0.5,
- 'stroke-opacity': 1,
- radius: 3,
- fill: '#1f78b4',
- 'fill-opacity': 0.2
- },
- tooltip_cols: null,
- custom_marks: null,
-
- //multiples
- multiples_sizing: {
- width: 300,
- height: 100
- }
- };
- }
-
- function webchartsSettings() {
- return {
- x: {
- column: null, // set in ./syncSettings
- type: null, // set in ./syncSettings
- behavior: 'raw'
- },
- y: {
- column: null, // set in ./syncSettings
- stat: 'mean',
- type: 'linear',
- label: 'Value',
- behavior: 'raw'
- },
- marks: [
- {
- per: null, // set in ./syncSettings
- type: 'line',
- attributes: {
- 'clip-path': null // set in ./syncSettings
- },
- tooltip: null, // set in ./syncSettings
- default: true
- },
- {
- per: null, // set in ./syncSettings
- type: 'circle',
- attributes: {
- 'clip-path': null // set in ./syncSettings
- },
- tooltip: null, // set in ./syncSettings
- default: true
- }
- ],
- resizable: true,
- margin: {
- right: 30, // create space for box plot
- left: 60
- },
- gridlines: 'y',
- aspect: 3
- };
- }
-
- function syncSettings(settings) {
- var time_col = settings.time_cols[0];
-
- //handle a string arguments to array settings
- var array_settings = ['filters', 'details', 'tooltip_cols'];
- array_settings.forEach(function(s) {
- if (!(settings[s] instanceof Array))
- settings[s] = typeof settings[s] === 'string' ? [settings[s]] : [];
- });
-
- //x-axis
- settings.x.column = time_col.value_col;
- settings.x.type = time_col.type;
- settings.x.label = time_col.label;
- settings.x.order = time_col.order;
-
- //y-axis
- settings.y.column = settings.value_col;
-
- //lines
- var lines = settings.marks.find(function(mark) {
- return mark.type === 'line';
- });
- lines.per = [settings.id_col, settings.measure_col];
- lines.tooltip = '[' + settings.id_col + ']';
- Object.assign(lines.attributes, settings.line_attributes);
- lines.attributes['stroke-width'] = settings.line_attributes['stroke-width'] || 0.5;
-
- //points
- var points = settings.marks.find(function(mark) {
- return mark.type === 'circle';
- });
- points.per = [
- settings.id_col,
- settings.measure_col,
- time_col.value_col,
- settings.value_col
- ];
- points.tooltip =
- 'Participant = [' +
- settings.id_col +
- ']\n[' +
- settings.measure_col +
- '] = [' +
- settings.value_col +
- '] [' +
- settings.unit_col +
- ']\n' +
- settings.x.label +
- ' = [' +
- settings.x.column +
- ']';
-
- //add custom tooltip values
- if (settings.tooltip_cols) {
- settings.tooltip_cols.forEach(function(tooltip) {
- var obj =
- typeof tooltip == 'string' ? { label: tooltip, value_col: tooltip } : tooltip;
- points.tooltip = points.tooltip + ('\n' + obj.label + ' = [' + obj.value_col + ']');
- });
- }
-
- Object.assign(points.attributes, settings.point_attributes);
- points.radius = settings.point_attributes.radius || 3;
-
- //Add custom marks to settings.marks.
- if (Array.isArray(settings.custom_marks) && settings.custom_marks.length)
- settings.custom_marks.forEach(function(mark) {
- if (mark instanceof Object) {
- mark.default = false; // distinguish custom marks from default marks
- if (mark.type === 'line')
- mark.attributes = Object.assign({}, lines.attributes, mark.attributes);
- else if (mark.type === 'circle') {
- mark.attributes = Object.assign({}, points.attributes, mark.attributes);
- mark.radius = mark.radius || points.radius;
- }
- settings.marks.push(mark);
- }
- });
-
- //Define margins for box plot and rotated x-axis tick labels.
- if (settings.margin) settings.margin.bottom = time_col.vertical_space;
- else
- settings.margin = {
- right: 20,
- bottom: time_col.vertical_space
- };
-
- settings.rotate_x_tick_labels = time_col.rotate_tick_labels;
-
- //Convert unscheduled_visit_pattern from string to regular expression.
- if (
- typeof settings.unscheduled_visit_pattern === 'string' &&
- settings.unscheduled_visit_pattern !== ''
- ) {
- var flags = settings.unscheduled_visit_pattern.replace(/.*?\/([gimy]*)$/, '$1'),
- pattern = settings.unscheduled_visit_pattern.replace(
- new RegExp('^/(.*?)/' + flags + '$'),
- '$1'
- );
- settings.unscheduled_visit_regex = new RegExp(pattern, flags);
- }
-
- return settings;
- }
-
- function controlInputs() {
- return [
- {
- type: 'subsetter',
- value_col: 'soe_measure', // set in syncControlInputs()
- label: 'Measure',
- start: null
- },
- {
- type: 'dropdown',
- option: 'x.column',
- label: 'X-axis',
- require: true
- },
- {
- type: 'number',
- option: 'y.domain[0]',
- label: 'Lower',
- require: true
- },
- {
- type: 'number',
- option: 'y.domain[1]',
- label: 'Upper',
- require: true
- },
- {
- type: 'dropdown',
- option: 'normal_range_method',
- label: 'Method',
- values: ['None', 'LLN-ULN', 'Standard Deviation', 'Quantiles'],
- require: true
- },
- {
- type: 'number',
- option: 'normal_range_sd',
- label: '# Std. Dev.'
- },
- {
- type: 'number',
- label: 'Lower',
- option: 'normal_range_quantile_low'
- },
- {
- type: 'number',
- label: 'Upper',
- option: 'normal_range_quantile_high'
- },
- {
- type: 'checkbox',
- inline: true,
- option: 'visits_without_data',
- label: 'Without Data'
- },
- {
- type: 'checkbox',
- inline: true,
- option: 'unscheduled_visits',
- label: 'Unscheduled'
- }
- ];
- }
-
- function syncControlInputs(controlInputs, settings) {
- var xAxisControl = controlInputs.find(function(d) {
- return d.label === 'X-axis';
- });
- xAxisControl.values = settings.time_cols.map(function(d) {
- return d.value_col;
- });
-
- if (settings.filters) {
- settings.filters.forEach(function(d, i) {
- var thisFilter = {
- type: 'subsetter',
- value_col: d.value_col ? d.value_col : d,
- label: d.label ? d.label : d.value_col ? d.value_col : d
- };
- //add the filter to the control inputs (as long as it isn't already there)
- var current_value_cols = controlInputs
- .filter(function(f) {
- return f.type == 'subsetter';
- })
- .map(function(m) {
- return m.value_col;
- });
- if (current_value_cols.indexOf(thisFilter.value_col) == -1)
- controlInputs.splice(4 + i, 0, thisFilter);
- });
- }
-
- //Remove unscheduled visit control if unscheduled visit pattern is unscpecified.
- if (
- !settings.unscheduled_visit_regex &&
- !(
- Array.isArray(settings.unscheduled_visit_values) &&
- settings.unscheduled_visit_values.length
- )
- )
- controlInputs.splice(
- controlInputs
- .map(function(controlInput) {
- return controlInput.label;
- })
- .indexOf('Unscheduled Visits'),
- 1
- );
-
- return controlInputs;
- }
-
- var configuration = {
- rendererSettings: rendererSettings,
- webchartsSettings: webchartsSettings,
- settings: Object.assign({}, rendererSettings(), webchartsSettings()),
- syncSettings: syncSettings,
- controlInputs: controlInputs,
- syncControlInputs: syncControlInputs
- };
-
- function countParticipants() {
- var _this = this;
-
- this.participantCount = {
- N: d3$1
- .set(
- this.raw_data.map(function(d) {
- return d[_this.config.id_col];
- })
- )
- .values()
- .filter(function(value) {
- return !/^\s*$/.test(value);
- }).length,
- container: null, // set in ../onLayout/addParticipantCountContainer
- n: null, // set in ../onDraw/updateParticipantCount
- percentage: null // set in ../onDraw/updateParticipantCount
- };
- }
-
- function removeMissingResults() {
- var _this = this;
-
- //Split data into records with missing and nonmissing results.
- var missingResults = [];
- var nonMissingResults = [];
- this.raw_data.forEach(function(d) {
- if (/^\s*$/.test(d[_this.config.value_col])) missingResults.push(d);
- else nonMissingResults.push(d);
- });
-
- //Nest missing and nonmissing results by participant.
- var participantsWithMissingResults = d3$1
- .nest()
- .key(function(d) {
- return d[_this.config.id_col];
- })
- .rollup(function(d) {
- return d.length;
- })
- .entries(missingResults);
- var participantsWithNonMissingResults = d3$1
- .nest()
- .key(function(d) {
- return d[_this.config.id_col];
- })
- .rollup(function(d) {
- return d.length;
- })
- .entries(nonMissingResults);
-
- //Identify placeholder records, i.e. participants with a single missing result.
- this.removedRecords.placeholderRecords = participantsWithMissingResults
- .filter(function(d) {
- return (
- participantsWithNonMissingResults
- .map(function(d) {
- return d.key;
- })
- .indexOf(d.key) < 0 && d.values === 1
- );
- })
- .map(function(d) {
- return d.key;
- });
- if (this.removedRecords.placeholderRecords.length)
- console.log(
- this.removedRecords.placeholderRecords.length +
- ' participants without results have been detected.'
- );
-
- //Count the number of records with missing results.
- this.removedRecords.missing = d3$1.sum(
- participantsWithMissingResults.filter(function(d) {
- return _this.removedRecords.placeholderRecords.indexOf(d.key) < 0;
- }),
- function(d) {
- return d.values;
- }
- );
- if (this.removedRecords.missing > 0)
- console.warn(
- this.removedRecords.missing +
- ' record' +
- (this.removedRecords.missing > 1
- ? 's with a missing result have'
- : ' with a missing result has') +
- ' been removed.'
- );
-
- //Update data.
- this.raw_data = nonMissingResults;
- }
-
- function removeNonNumericResults() {
- var _this = this;
-
- //Filter out non-numeric results.
- var numericResults = this.raw_data.filter(function(d) {
- return /^-?[0-9.]+$/.test(d[_this.config.value_col]);
- });
- this.removedRecords.nonNumeric = this.raw_data.length - numericResults.length;
- if (this.removedRecords.nonNumeric > 0)
- console.warn(
- this.removedRecords.nonNumeric +
- ' record' +
- (this.removedRecords.nonNumeric > 1
- ? 's with a non-numeric result have'
- : ' with a non-numeric result has') +
- ' been removed.'
- );
-
- //Update data.
- this.raw_data = numericResults;
- }
-
- function cleanData() {
- this.removedRecords = {
- placeholderParticipants: null, // defined in './cleanData/removeMissingResults
- missing: null, // defined in './cleanData/removeMissingResults
- nonNumeric: null, // defined in './cleanData/removeNonNumericResults
- container: null // defined in ../onLayout/addRemovedRecordsContainer
- };
- removeMissingResults.call(this);
- removeNonNumericResults.call(this);
- this.initial_data = this.raw_data;
- }
-
- function addVariables() {
- var _this = this;
-
- var ordinalTimeSettings = this.config.time_cols.find(function(time_col) {
- return time_col.type === 'ordinal';
- });
-
- this.raw_data.forEach(function(d) {
- //Concatenate unit to measure if provided.
- d.soe_measure = d.hasOwnProperty(_this.config.unit_col)
- ? d[_this.config.measure_col] + ' (' + d[_this.config.unit_col] + ')'
- : d[_this.config.measure_col];
-
- //Identify unscheduled visits.
- d.unscheduled = false;
- if (ordinalTimeSettings) {
- if (_this.config.unscheduled_visit_values)
- d.unscheduled =
- _this.config.unscheduled_visit_values.indexOf(
- d[ordinalTimeSettings.value_col]
- ) > -1;
- else if (_this.config.unscheduled_visit_regex)
- d.unscheduled = _this.config.unscheduled_visit_regex.test(
- d[ordinalTimeSettings.value_col]
- );
- }
- });
- }
-
- function participant() {
- var _this = this;
-
- this.IDOrder = d3$1
- .set(
- this.raw_data.map(function(d) {
- return d[_this.config.id_col];
- })
- )
- .values()
- .sort()
- .map(function(ID, i) {
- return {
- ID: ID,
- order: i
- };
- });
- }
-
- function visit() {
- var _this = this;
-
- //ordinal
- this.config.time_cols
- .filter(function(time_col) {
- return time_col.type === 'ordinal';
- })
- .forEach(function(time_settings) {
- var visits = void 0,
- visitOrder = void 0;
-
- //Given an ordering variable sort a unique set of visits by the ordering variable.
- if (
- time_settings.order_col &&
- _this.raw_data[0].hasOwnProperty(time_settings.order_col)
- ) {
- //Define a unique set of visits with visit order concatenated.
- visits = d3$1
- .set(
- _this.raw_data.map(function(d) {
- return (
- d[time_settings.order_col] + '|' + d[time_settings.value_col]
- );
- })
- )
- .values();
-
- //Sort visits.
- visitOrder = visits
- .sort(function(a, b) {
- var aOrder = a.split('|')[0],
- bOrder = b.split('|')[0],
- diff = +aOrder - +bOrder;
- return diff ? diff : d3$1.ascending(a, b);
- })
- .map(function(visit) {
- return visit.split('|')[1];
- });
- } else {
- //Otherwise sort a unique set of visits alphanumerically.
- //Define a unique set of visits.
- visits = d3$1
- .set(
- _this.raw_data.map(function(d) {
- return d[time_settings.value_col];
- })
- )
- .values();
-
- //Sort visits;
- visitOrder = visits.sort();
- }
-
- //Set x-axis domain.
- if (time_settings.order) {
- //If a visit order is specified, use it and concatenate any unspecified visits at the end.
- time_settings.order = time_settings.order.concat(
- visitOrder.filter(function(visit) {
- return time_settings.order.indexOf(visit) < 0;
- })
- );
- }
- //Otherwise use data-driven visit order.
- else time_settings.order = visitOrder;
-
- //Define domain.
- time_settings.domain = time_settings.order;
- });
- }
-
- function measure() {
- var _this = this;
-
- this.measures = d3$1
- .set(
- this.initial_data.map(function(d) {
- return d[_this.config.measure_col];
- })
- )
- .values()
- .sort();
- this.soe_measures = d3$1
- .set(
- this.initial_data.map(function(d) {
- return d.soe_measure;
- })
- )
- .values()
- .sort();
- }
-
- function defineSets() {
- participant.call(this);
- visit.call(this);
- measure.call(this);
- }
-
- function updateMeasureFilter() {
- this.measure = {};
- var measureInput = this.controls.config.inputs.find(function(input) {
- return input.label === 'Measure';
- });
- if (
- this.config.start_value &&
- this.soe_measures.indexOf(this.config.start_value) < 0 &&
- this.measures.indexOf(this.config.start_value) < 0
- ) {
- measureInput.start = this.soe_measures[0];
- console.warn(
- this.config.start_value +
- ' is an invalid measure. Defaulting to ' +
- measureInput.start +
- '.'
- );
- } else if (
- this.config.start_value &&
- this.soe_measures.indexOf(this.config.start_value) < 0
- ) {
- measureInput.start = this.soe_measures[this.measures.indexOf(this.config.start_value)];
- console.warn(
- this.config.start_value +
- ' is missing the units value. Defaulting to ' +
- measureInput.start +
- '.'
- );
- } else measureInput.start = this.config.start_value || this.soe_measures[0];
- }
-
- function removeFilters() {
- var _this = this;
-
- this.controls.config.inputs = this.controls.config.inputs.filter(function(input) {
- if (input.type !== 'subsetter' || input.value_col === 'soe_measure') {
- return true;
- } else if (!_this.raw_data[0].hasOwnProperty(input.value_col)) {
- console.warn(
- 'The [ ' +
- input.label +
- ' ] filter has been removed because the variable does not exist.'
- );
- } else {
- var levels = d3$1
- .set(
- _this.raw_data.map(function(d) {
- return d[input.value_col];
- })
- )
- .values();
-
- if (levels.length === 1)
- console.warn(
- 'The [ ' +
- input.label +
- ' ] filter has been removed because the variable has only one level.'
- );
-
- return levels.length > 1;
- }
- });
- }
-
- function updateNormalRangeControl() {
- //If data do not have normal range variables update normal range method setting and options.
- if (
- Object.keys(this.raw_data[0]).indexOf(this.config.normal_col_low) < 0 ||
- Object.keys(this.raw_data[0]).indexOf(this.config.normal_col_high) < 0
- ) {
- if (this.config.normal_range_method === 'LLN-ULN')
- this.config.normal_range_method = 'Standard Deviation';
- this.controls.config.inputs
- .find(function(input) {
- return input.option === 'normal_range_method';
- })
- .values.splice(1, 1);
- }
- }
-
- function checkControls() {
- updateMeasureFilter.call(this);
- removeFilters.call(this);
- updateNormalRangeControl.call(this);
- }
-
- function initCustomEvents() {
- var chart = this;
- chart.participantsSelected = [];
- chart.events.participantsSelected = new CustomEvent('participantsSelected');
- }
-
- function onInit() {
- // 1. Count number of unique participant IDs in data prior to data cleaning.
- countParticipants.call(this);
-
- // 2. Remove missing and non-numeric results.
- cleanData.call(this);
-
- // 3. Define additional variables.
- addVariables.call(this);
-
- // 4. Define participant, visit, and measure sets.
- defineSets.call(this);
-
- // 5. Check controls.
- checkControls.call(this);
-
- // 6. Initialize custom events
- initCustomEvents.call(this);
- }
-
- function identifyControls() {
- var controlGroups = this.controls.wrap
- .style('padding-bottom', '8px')
- .selectAll('.control-group');
-
- //Give each control a unique ID.
- controlGroups
- .attr('id', function(d) {
- return d.label.toLowerCase().replace(' ', '-');
- })
- .each(function(d) {
- d3$1.select(this).classed(d.type, true);
- });
-
- //Give y-axis controls a common class name.
- controlGroups
- .filter(function(d) {
- return ['y.domain[0]', 'y.domain[1]'].indexOf(d.option) > -1;
- })
- .classed('y-axis', true);
-
- //Give normal range controls a common class name.
- controlGroups
- .filter(function(d) {
- return (
- [
- 'normal_range_method',
- 'normal_range_sd',
- 'normal_range_quantile_low',
- 'normal_range_quantile_high'
- ].indexOf(d.option) > -1
- );
- })
- .classed('normal-range', true);
-
- //Give visit range controls a common class name.
- controlGroups
- .filter(function(d) {
- return ['visits_without_data', 'unscheduled_visits'].indexOf(d.option) > -1;
- })
- .classed('visits', true);
- }
-
- function labelXaxisOptions() {
- var _this = this;
-
- this.controls.wrap
- .selectAll('.control-group')
- .filter(function(d) {
- return d.option === 'x.column';
- })
- .selectAll('option')
- .property('label', function(d) {
- return _this.config.time_cols.find(function(time_col) {
- return time_col.value_col === d;
- }).label;
- });
- }
-
- function addYdomainResetButton() {
- var _this = this;
-
- var resetContainer = this.controls.wrap
- .insert('div', '#lower')
- .classed('control-group y-axis', true)
- .datum({
- type: 'button',
- option: 'y.domain',
- label: ''
- })
- .style('vertical-align', 'bottom');
- var resetLabel = resetContainer
- .append('span')
- .attr('class', 'wc-control-label')
- .text('Limits');
- var resetButton = resetContainer
- .append('button')
- .text(' Reset ')
- .style('padding', '0px 5px')
- .on('click', function() {
- _this.config.y.domain = _this.measure.domain; //reset axis to full range
- _this.draw();
- });
- }
-
- function insertGrouping(selector, label) {
- var grouping = this.controls.wrap
- .insert('div', selector)
- .style({
- display: 'inline-block',
- 'margin-right': '5px'
- })
- .append('fieldset')
- .style('padding', '0px 2px');
- grouping.append('legend').text(label);
- this.controls.wrap.selectAll(selector).each(function(d) {
- this.style.marginTop = '0px';
- this.style.marginRight = '2px';
- this.style.marginBottom = '2px';
- this.style.marginLeft = '2px';
- grouping.node().appendChild(this);
- });
- }
-
- function groupControls() {
- //Group y-axis controls.
- insertGrouping.call(this, '.y-axis', 'Y-axis');
-
- //Group filters.
- if (this.filters.length > 1)
- insertGrouping.call(this, '.subsetter:not(#measure)', 'Filters');
-
- //Group normal controls.
- insertGrouping.call(this, '.normal-range', 'Normal Range');
-
- //Group visit controls.
- insertGrouping.call(this, '.visits', 'Visits');
- }
-
- function hideNormalRangeInputs() {
- var _this = this;
- var controls = this.controls.wrap.selectAll('.control-group');
-
- //Normal range method control
- var normalRangeMethodControl = controls.filter(function(d) {
- return d.label === 'Method';
- });
-
- //Normal range inputs
- var normalRangeInputs = controls
- .filter(function(d) {
- return (
- [
- 'normal_range_sd',
- 'normal_range_quantile_low',
- 'normal_range_quantile_high'
- ].indexOf(d.option) > -1
- );
- })
- .style('display', function(d) {
- return (_this.config.normal_range_method !== 'Standard Deviation' &&
- d.option === 'normal_range_sd') ||
- (_this.config.normal_range_method !== 'Quantiles' &&
- ['normal_range_quantile_low', 'normal_range_quantile_high'].indexOf(
- d.option
- ) > -1)
- ? 'none'
- : 'inline-table';
- });
-
- //Set significant digits to .01.
- normalRangeInputs.select('input').attr('step', 0.01);
-
- normalRangeMethodControl.on('change', function() {
- var normal_range_method = d3$1
- .select(this)
- .select('option:checked')
- .text();
-
- normalRangeInputs.style('display', function(d) {
- return (normal_range_method !== 'Standard Deviation' &&
- d.option === 'normal_range_sd') ||
- (normal_range_method !== 'Quantiles' &&
- ['normal_range_quantile_low', 'normal_range_quantile_high'].indexOf(
- d.option
- ) > -1)
- ? 'none'
- : 'inline-table';
- });
- });
- }
-
- function addParticipantCountContainer() {
- this.participantCount.container = this.controls.wrap
- .style('position', 'relative')
- .append('div')
- .attr('id', 'participant-count')
- .style({
- position: 'absolute',
- 'font-style': 'italic',
- bottom: '-10px',
- left: 0
- });
- }
-
- function addRemovedRecordsNote() {
- var _this = this;
-
- if (this.removedRecords.missing > 0 || this.removedRecords.nonNumeric > 0) {
- var message =
- this.removedRecords.missing > 0 && this.removedRecords.nonNumeric > 0
- ? this.removedRecords.missing +
- ' record' +
- (this.removedRecords.missing > 1 ? 's' : '') +
- ' with a missing result and ' +
- this.removedRecords.nonNumeric +
- ' record' +
- (this.removedRecords.nonNumeric > 1 ? 's' : '') +
- ' with a non-numeric result were removed.'
- : this.removedRecords.missing > 0
- ? this.removedRecords.missing +
- ' record' +
- (this.removedRecords.missing > 1 ? 's' : '') +
- ' with a missing result ' +
- (this.removedRecords.missing > 1 ? 'were' : 'was') +
- ' removed.'
- : this.removedRecords.nonNumeric > 0
- ? this.removedRecords.nonNumeric +
- ' record' +
- (this.removedRecords.nonNumeric > 1 ? 's' : '') +
- ' with a non-numeric result ' +
- (this.removedRecords.nonNumeric > 1 ? 'were' : 'was') +
- ' removed.'
- : '';
- this.removedRecords.container = this.controls.wrap
- .append('div')
- .style({
- position: 'absolute',
- 'font-style': 'italic',
- bottom: '-10px',
- right: 0
- })
- .text(message);
- this.removedRecords.container
- .append('span')
- .style({
- color: 'blue',
- 'text-decoration': 'underline',
- 'font-style': 'normal',
- 'font-weight': 'bold',
- cursor: 'pointer',
- 'font-size': '16px',
- 'margin-left': '5px'
- })
- .html('x')
- .on('click', function() {
- return _this.removedRecords.container.style('display', 'none');
- });
- }
- }
-
- function addBorderAboveChart() {
- this.wrap.style({
- 'border-top': '1px solid #ccc'
- });
- }
-
- function addSmallMultiplesContainer() {
- this.multiples = {
- container: this.wrap
- .append('div')
- .classed('multiples', true)
- .style({
- 'padding-top': '10px'
- }),
- id: null
- };
- }
-
- function onLayout() {
- identifyControls.call(this); // Distinguish controls to insert y-axis reset button in the correct position.
- labelXaxisOptions.call(this);
- addYdomainResetButton.call(this);
- groupControls.call(this); // Group related controls visually.
- hideNormalRangeInputs.call(this); // Hide normal range input controls depending on the normal range method.
- addParticipantCountContainer.call(this);
- addRemovedRecordsNote.call(this);
- addBorderAboveChart.call(this);
- addSmallMultiplesContainer.call(this);
- }
-
- function getCurrentMeasure() {
- this.measure.previous = this.measure.current;
- this.measure.current = this.controls.wrap
- .selectAll('.control-group')
- .filter(function(d) {
- return d.value_col && d.value_col === 'soe_measure';
- })
- .select('option:checked')
- .text();
- }
-
- function defineMeasureData() {
- var _this = this;
-
- this.measure.data = this.initial_data.filter(function(d) {
- return d.soe_measure === _this.measure.current;
- });
- this.measure.unit =
- this.config.unit_col && this.measure.data[0].hasOwnProperty(this.config.unit_col)
- ? this.measure.data[0][this.config.unit_col]
- : null;
- this.measure.results = this.measure.data
- .map(function(d) {
- return +d[_this.config.value_col];
- })
- .sort(function(a, b) {
- return a - b;
- });
- this.measure.domain = d3$1.extent(this.measure.results);
- this.measure.range = this.measure.domain[1] - this.measure.domain[0];
- this.measure.log10range = Math.log10(this.measure.range);
- this.raw_data = this.measure.data.filter(function(d) {
- return _this.config.unscheduled_visits || !d.unscheduled;
- });
- }
-
- function removeVisitsWithoutData() {
- var _this = this;
-
- if (!this.config.visits_without_data)
- this.config.x.domain = this.config.x.domain.filter(function(visit) {
- return (
- d3$1
- .set(
- _this.raw_data.map(function(d) {
- return d[_this.config.time_settings.value_col];
- })
- )
- .values()
- .indexOf(visit) > -1
- );
- });
- }
-
- function removeUnscheduledVisits() {
- var _this = this;
-
- if (!this.config.unscheduled_visits) {
- if (this.config.unscheduled_visit_values)
- this.config.x.domain = this.config.x.domain.filter(function(visit) {
- return _this.config.unscheduled_visit_values.indexOf(visit) < 0;
- });
- else if (this.config.unscheduled_visit_regex)
- this.config.x.domain = this.config.x.domain.filter(function(visit) {
- return !_this.config.unscheduled_visit_regex.test(visit);
- });
- }
- }
-
- function setXdomain() {
- var _this = this;
-
- //Attach the time settings object to the x-axis settings object.
- this.config.time_settings = this.config.time_cols.find(function(time_col) {
- return time_col.value_col === _this.config.x.column;
- });
- Object.assign(this.config.x, this.config.time_settings);
-
- //When the domain is not specified, it's calculated on data transform.
- if (this.config.x.type === 'linear') {
- delete this.config.x.domain;
- delete this.config.x.order;
- }
-
- //Remove unscheduled visits from x-domain if x-type is ordinal.
- if (this.config.x.type === 'ordinal') {
- removeVisitsWithoutData.call(this);
- removeUnscheduledVisits.call(this);
- }
- }
-
- function setYdomain() {
- if (this.measure.current !== this.measure.previous)
- this.config.y.domain = this.measure.domain;
- else if (this.config.y.domain[0] > this.config.y.domain[1])
- // reset y-domain
- this.config.y.domain.reverse(); // reverse y-domain
- }
-
- function calculateYPrecision() {
- //define the precision of the y-axis
- this.config.y.precisionFactor = Math.round(this.measure.log10range);
- this.config.y.precision = Math.pow(10, this.config.y.precisionFactor);
- this.config.y.format =
- this.config.y.precisionFactor > 0
- ? '.0f'
- : '.' + (Math.abs(this.config.y.precisionFactor) + 1) + 'f';
-
- //define the size of the y-axis limit increments
- var step =
- this.measure.range > 0
- ? Math.abs(this.measure.range / 15) // non-zero range
- : this.measure.results[0] !== 0
- ? Math.abs(this.measure.results[0] / 15) // zero range, non-zero result(s)
- : 1; // zero range, zero result(s)
- if (step < 1) {
- var x10 = 0;
- do {
- step = step * 10;
- ++x10;
- } while (step < 1);
- step = Math.round(step) / Math.pow(10, x10);
- } else step = Math.round(step);
- this.measure.step = step || 1;
- }
-
- function updateYaxisLimitControls() {
- this.controls.wrap
- .selectAll('.control-group')
- .filter(function(f) {
- return f.option === 'y.domain[0]';
- })
- .select('input')
- .attr('step', this.measure.step) // set in ./calculateYPrecision
- .style('box-shadow', 'none')
- .property('value', this.config.y.domain[0]);
-
- this.controls.wrap
- .selectAll('.control-group')
- .filter(function(f) {
- return f.option === 'y.domain[1]';
- })
- .select('input')
- .attr('step', this.measure.step) // set in ./calculateYPrecision
- .style('box-shadow', 'none')
- .property('value', this.config.y.domain[1]);
- }
-
- function setYaxisLabel() {
- this.config.y.label = this.measure.current;
- }
-
- function updateYaxisResetButton() {
- //Update tooltip of y-axis domain reset button.
- if (this.currentMeasure !== this.previousMeasure)
- this.controls.wrap
- .selectAll('.y-axis')
- .property(
- 'title',
- 'Initial Limits: [' +
- this.config.y.domain[0] +
- ' - ' +
- this.config.y.domain[1] +
- ']'
- );
- }
-
- function deriveStatistics() {
- var _this = this;
-
- if (this.config.normal_range_method === 'LLN-ULN') {
- this.lln = function(d) {
- return d instanceof Object
- ? +d[_this.config.normal_col_low]
- : d3$1.median(_this.measure.data, function(d) {
- return +d[_this.config.normal_col_low];
- });
- };
- this.uln = function(d) {
- return d instanceof Object
- ? +d[_this.config.normal_col_high]
- : d3$1.median(_this.measure.data, function(d) {
- return +d[_this.config.normal_col_high];
- });
- };
- } else if (this.config.normal_range_method === 'Standard Deviation') {
- this.mean = d3$1.mean(this.measure.results);
- this.sd = d3$1.deviation(this.measure.results);
- this.lln = function() {
- return _this.mean - _this.config.normal_range_sd * _this.sd;
- };
- this.uln = function() {
- return _this.mean + _this.config.normal_range_sd * _this.sd;
- };
- } else if (this.config.normal_range_method === 'Quantiles') {
- this.lln = function() {
- return d3$1.quantile(_this.measure.results, _this.config.normal_range_quantile_low);
- };
- this.uln = function() {
- return d3$1.quantile(
- _this.measure.results,
- _this.config.normal_range_quantile_high
- );
- };
- } else {
- this.lln = function(d) {
- return d instanceof Object
- ? d[_this.config.value_col] + 1
- : _this.measure.results[0];
- };
- this.uln = function(d) {
- return d instanceof Object
- ? d[_this.config.value_col] - 1
- : _this.measure.results[_this.measure.results.length - 1];
- };
- }
- }
-
- function onPreprocess() {
- // 1. Capture currently selected measure.
- getCurrentMeasure.call(this);
-
- // 2. Filter data on currently selected measure.
- defineMeasureData.call(this);
-
- // 3a Set x-domain given current visit settings.
- setXdomain.call(this);
-
- // 3b Set y-domain given currently selected measure.
- setYdomain.call(this);
-
- // 3c Calculate precision of y-domain.
- calculateYPrecision.call(this);
-
- // 3c Set y-axis label to current measure.
- setYaxisLabel.call(this);
-
- // 4a Update y-axis reset button when measure changes.
- updateYaxisResetButton.call(this);
-
- // 4b Update y-axis limit controls to match y-axis domain.
- updateYaxisLimitControls.call(this);
-
- // 4c Define normal range statistics.
- deriveStatistics.call(this);
- }
-
- function onDatatransform() {}
-
- function updateParticipantCount() {
- var _this = this;
-
- //count the number of unique ids in the current chart and calculate the percentage
- this.participantCount.n = d3$1
- .set(
- this.filtered_data.map(function(d) {
- return d[_this.config.id_col];
- })
- )
- .values().length;
- this.participantCount.percentage = d3$1.format('0.1%')(
- this.participantCount.n / this.participantCount.N
- );
-
- //clear the annotation
- this.participantCount.container.selectAll('*').remove();
-
- //update the annotation
- this.participantCount.container.text(
- '\n' +
- this.participantCount.n +
- ' of ' +
- this.participantCount.N +
- ' participant(s) shown (' +
- this.participantCount.percentage +
- ')'
- );
- }
-
- function resetChart() {
- this.svg.selectAll('.line,.point').remove();
- this.wrap.select('div.overlapNote').remove();
- //delete this.hovered_id;
- //delete this.selected_id;
- //if (this.multiples.chart)
- // this.multiples.chart.destroy();
- }
-
- function extendYDomain() {
- if (
- this.config.y.domain[0] === this.measure.domain[0] &&
- this.config.y.domain[1] === this.measure.domain[1] &&
- this.config.y.domain[0] < this.measure.domain[1]
- )
- this.y_dom = [
- this.config.y.domain[0] - this.measure.range * 0.01,
- this.config.y.domain[1] + this.measure.range * 0.01
- ];
- }
-
- function updateBottomMargin() {
- this.config.margin.bottom = this.config.x.vertical_space;
- }
-
- function onDraw() {
- //Annotate participant count.
- updateParticipantCount.call(this);
-
- //Clear current multiples.
- resetChart.call(this);
-
- //Extend y-domain to avoid obscuring minimum and maximum points.
- extendYDomain.call(this);
-
- //Update bottom margin for tick label rotation.
- updateBottomMargin.call(this);
- }
-
- function attachMarks() {
- this.marks.forEach(function(mark) {
- mark.groups.each(function(group) {
- group.attributes = mark.attributes;
- if (mark.type === 'circle') group.radius = mark.radius;
- });
- });
- this.lines = this.svg.selectAll('.line');
- this.points = this.svg.selectAll('.point');
- }
-
- function highlightSelected() {
- var _this = this;
-
- //Add _selected_ class to participant's marks.
- this.marks.forEach(function(mark) {
- mark.groups.classed('selected', function(d) {
- return mark.type === 'line'
- ? d.values[0].values.raw[0][_this.config.id_col] === _this.selected_id
- : d.values.raw[0][_this.config.id_col] === _this.selected_id;
- });
- });
-
- //Update attributes of selected line.
- this.lines
- .filter(function(d) {
- return d.values[0].values.raw[0][_this.config.id_col] === _this.selected_id;
- })
- .select('path')
- .attr('stroke-width', function(d) {
- return d.attributes['stroke-width'] * 8;
- });
-
- //Update attributes of selected points.
- this.points
- .filter(function(d) {
- return d.values.raw[0][_this.config.id_col] === _this.selected_id;
- })
- .select('circle')
- .attr({
- r: function r(d) {
- return d.radius;
- },
- stroke: 'black',
- 'stroke-width': function strokeWidth(d) {
- return d.attributes['stroke-width'] * 8;
- }
- });
- }
-
- function maintainHighlight() {
- if (this.selected_id) highlightSelected.call(this);
- }
-
- function drawNormalRange() {
- if (this.normalRange) this.normalRange.remove();
-
- if (this.config.normal_range_method) {
- this.normalRange = this.svg
- .insert('g', '.line-supergroup')
- .classed('normal-range', true);
- this.normalRange
- .append('rect')
- .attr({
- x: 0,
- y: this.y(this.uln()),
- width: this.plot_width,
- height: this.y(this.lln()) - this.y(this.uln()),
- 'clip-path': 'url(#' + this.id + ')'
- })
- .style({
- fill: 'blue',
- 'fill-opacity': 0.1
- });
- this.normalRange.append('title').text('Normal range: ' + this.lln() + '-' + this.uln());
- }
- }
-
- function clearHovered() {
- this.lines
- .filter(function() {
- return !d3$1.select(this).classed('selected');
- })
- .select('path')
- .each(function(d) {
- d3$1.select(this).attr(d.attributes);
- });
- this.points
- .filter(function() {
- return !d3$1.select(this).classed('selected');
- })
- .select('circle')
- .each(function(d) {
- d3$1.select(this).attr(d.attributes);
- d3$1.select(this).attr('r', d.radius);
- });
- delete this.hovered_id;
- }
-
- function clearSelected() {
- this.marks.forEach(function(mark) {
- var element = mark.type === 'line' ? 'path' : mark.type;
- mark.groups
- .classed('selected', false)
- .select(element)
- .attr(mark.attributes);
- });
- if (this.multiples.chart) this.multiples.chart.destroy();
- delete this.selected_id;
-
- //Trigger participantsSelected event
- this.participantsSelected = [];
- this.events.participantsSelected.data = this.participantsSelected;
- this.wrap.node().dispatchEvent(this.events.participantsSelected);
- }
-
- function addOverlayEventListener() {
- var _this = this;
-
- var context = this;
- this.overlay
- .on('mouseover', function() {
- clearHovered.call(_this);
- })
- .on('click', function() {
- clearHovered.call(_this);
- clearSelected.call(_this);
- context.wrap.select('div.overlapNote').remove();
- });
- }
-
- function addOverlayEventListener$1() {
- var _this = this;
-
- this.normalRange
- .on('mouseover', function() {
- clearHovered.call(_this);
- })
- .on('click', function() {
- clearHovered.call(_this);
- clearSelected.call(_this);
- });
- }
-
- function highlightHovered() {
- var _this = this;
-
- //Update attributes of hovered line.
- this.lines
- .filter(function(d) {
- return d.values[0].values.raw[0][_this.config.id_col] === _this.hovered_id;
- })
- .select('path')
- .attr('stroke-width', function(d) {
- return d.attributes['stroke-width'] * 4;
- });
-
- //Update attributes of hovered points.
- this.points
- .filter(function(d) {
- return d.values.raw[0][_this.config.id_col] === _this.hovered_id;
- })
- .select('circle')
- .attr({
- r: function r(d) {
- return d.radius;
- },
- stroke: 'black',
- 'stroke-width': function strokeWidth(d) {
- return d.attributes['stroke-width'] * 4;
- }
- });
- }
-
- var _typeof =
- typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol'
- ? function(obj) {
- return typeof obj;
- }
- : function(obj) {
- return obj &&
- typeof Symbol === 'function' &&
- obj.constructor === Symbol &&
- obj !== Symbol.prototype
- ? 'symbol'
- : typeof obj;
- };
-
- /*------------------------------------------------------------------------------------------------\
- Clone a variable (http://stackoverflow.com/a/728694).
- \------------------------------------------------------------------------------------------------*/
-
- function clone(obj) {
- var copy;
-
- //Handle the 3 simple types, and null or undefined
- if (null == obj || 'object' != (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)))
- return obj;
-
- //Handle Date
- if (obj instanceof Date) {
- copy = new Date();
- copy.setTime(obj.getTime());
- return copy;
- }
-
- //Handle Array
- if (obj instanceof Array) {
- copy = [];
- for (var i = 0, len = obj.length; i < len; i++) {
- copy[i] = clone(obj[i]);
- }
- return copy;
- }
-
- //Handle Object
- if (obj instanceof Object) {
- copy = {};
- for (var attr in obj) {
- if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
- }
- return copy;
- }
-
- throw new Error("Unable to copy obj! Its type isn't supported.");
- }
-
- function defineSmallMultiples() {
- //Define small multiples settings.
- this.multiples.settings = Object.assign(
- {},
- clone(this.config),
- clone(Object.getPrototypeOf(this.config))
- );
- this.multiples.settings.x.domain = null;
- this.multiples.settings.y.domain = null;
- this.multiples.settings.resizable = false;
- this.multiples.settings.scale_text = false;
-
- if (this.multiples.settings.multiples_sizing.width)
- this.multiples.settings.width = this.multiples.settings.multiples_sizing.width;
- if (this.multiples.settings.multiples_sizing.height)
- this.multiples.settings.height =
- this.multiples.settings.multiples_sizing.height +
- (this.multiples.settings.margin.bottom ? this.multiples.settings.margin.bottom : 0);
-
- this.multiples.settings.margin = { bottom: this.multiples.settings.margin.bottom || 20 };
-
- //Add participant dropdown.
- this.multiples.settings.selected_id = this.selected_id;
- this.multiples.controls = webcharts.createControls(this.multiples.container.node(), {
- inputs: [
- {
- type: 'dropdown',
- label: 'All Measures for',
- option: 'selected_id',
- values: this.IDOrder.map(function(d) {
- return d.ID;
- }),
- require: true
- }
- ]
- });
-
- //Initialize small multiples.
- this.multiples.chart = webcharts.createChart(
- this.multiples.container.node(),
- this.multiples.settings,
- this.multiples.controls
- );
- this.multiples.chart.safetyOutlierExplorer = this;
- }
-
- function participantCharacteristics() {
- var _this = this;
-
- this.multiples.detail_table = this.multiples.chart.wrap
- .insert('table', '.legend')
- .append('tbody')
- .classed('detail-listing', true);
- this.multiples.detail_table
- .append('thead')
- .selectAll('th')
- .data(['', ''])
- .enter()
- .append('th');
- this.multiples.detail_table.append('tbody');
-
- //Insert a line for each item in [ settings.detail_cols ].
- if (Array.isArray(this.config.details) && this.config.details.length) {
- var participantDatum = this.multiples.data[0];
- this.config.details.forEach(function(detail) {
- var value_col = detail.value_col ? detail.value_col : detail;
- var label = detail.label
- ? detail.label
- : detail.value_col
- ? detail.value_col
- : detail;
- var tuple = [label, participantDatum[value_col]];
-
- if (tuple[1] !== undefined)
- _this.multiples.detail_table
- .select('tbody')
- .append('tr')
- .selectAll('td')
- .data(tuple)
- .enter()
- .append('td')
- .style('text-align', function(d, i) {
- return i === 0 ? 'right' : 'left';
- })
- .text(function(d, i) {
- return i === 0 ? d + ':' : d;
- });
- });
- }
- }
-
- function onLayout$1() {
- this.multiples.chart.on('layout', function() {
- //Define multiple styling.
- this.wrap.style('display', 'block');
- this.wrap
- .selectAll('.wc-chart-title')
- .style('display', 'block')
- .style('border-top', '1px solid #eee');
- this.wrap.selectAll('.wc-chart').style('padding-bottom', '2px');
-
- //Set y-label to measure unit.
- this.config.y.label = '';
-
- //Outline currently selected measure.
- //if (this.filters[0].val === this.parent.safetyOutlierExplorer.measure.current)
- // this.wrap
- // .select('.wc-chart-title')
- // .append('span')
- // .html(' ⓘ')
- // .style({
- // 'font-weight': 'bold',
- // 'cursor': 'default',
- // })
- // .attr('title', 'Currently selected measure');
- });
- }
-
- function onPreprocess$1() {
- this.multiples.chart.on('preprocess', function() {
- var _this = this;
-
- //Define y-domain as minimum of lower limit of normal and minimum result and maximum of
- //upper limit of normal and maximum result.
- var filtered_data = this.raw_data.filter(function(f) {
- return f[_this.filters[0].col] === _this.filters[0].val;
- });
-
- //Calculate range of normal range.
- var normlo = Math.min.apply(
- null,
- filtered_data
- .map(function(m) {
- return +m[_this.config.normal_col_low];
- })
- .filter(function(f) {
- return +f || +f === 0;
- })
- );
- var normhi = Math.max.apply(
- null,
- filtered_data
- .map(function(m) {
- return +m[_this.config.normal_col_high];
- })
- .filter(function(f) {
- return +f || +f === 0;
- })
- );
-
- //Calculate range of data.
- var ylo = d3$1.min(
- filtered_data
- .map(function(m) {
- return +m[_this.config.y.column];
- })
- .filter(function(f) {
- return +f || +f === 0;
- })
- );
- var yhi = d3$1.max(
- filtered_data
- .map(function(m) {
- return +m[_this.config.y.column];
- })
- .filter(function(f) {
- return +f || +f === 0;
- })
- );
-
- //Set y-domain.
- this.config.y_dom = [Math.min(normlo, ylo), Math.max(normhi, yhi)];
- });
- }
-
- function adjustTicks() {
- if (this.config.x.rotate_tick_labels)
- this.svg
- .selectAll('.x.axis .tick text')
- .attr({
- transform: 'rotate(-45)',
- dx: -10,
- dy: 10
- })
- .style('text-anchor', 'end');
- }
-
- function rangePolygon() {
- var _this = this;
-
- var area = d3$1.svg
- .area()
- .x(function(d) {
- return (
- _this.x(d['TIME']) +
- (_this.config.x.type === 'ordinal' ? _this.x.rangeBand() / 2 : 0)
- );
- })
- .y0(function(d) {
- return /^-?[0-9.]+$/.test(d[_this.config.normal_col_low])
- ? _this.y(d[_this.config.normal_col_low])
- : 0;
- })
- .y1(function(d) {
- return /^-?[0-9.]+$/.test(d[_this.config.normal_col_high])
- ? _this.y(d[_this.config.normal_col_high])
- : 0;
- });
-
- var dRow = this.filtered_data[0];
-
- var myRows = this.x_dom.slice().map(function(m) {
- return {
- STNRLO: dRow[_this.config.normal_col_low],
- STNRHI: dRow[_this.config.normal_col_high],
- TIME: m
- };
- });
-
- //remove what is there now
- this.svg.select('.norms').remove();
-
- //add new
- var normalRange = this.svg
- .append('g')
- .datum(myRows)
- .attr('class', 'norms');
- normalRange
- .append('path')
- .attr('fill', 'blue')
- .attr('fill-opacity', 0.1)
- .attr('d', area);
- normalRange.append('title').text(function(d) {
- return 'Normal range: ' + d[0].STNRLO + '-' + d[0].STNRHI;
- });
- }
-
- function onResize() {
- this.multiples.chart.on('resize', function() {
- //Resize text manually.
- this.wrap.select('.wc-chart-title').style('font-size', '12px');
- this.svg.selectAll('.axis .tick text').style('font-size', '10px');
-
- //Draw normal range.
- if (this.filtered_data.length) rangePolygon.call(this);
-
- //Axis tweaks
- this.svg
- .select('.x.axis')
- .select('.axis-title')
- .remove();
-
- //Delete legend.
- this.legend.remove();
-
- //Rotate ticks.
- adjustTicks.call(this);
- });
- }
-
- function updateParticipantDropdown() {
- var context = this; // chart
-
- var participantDropdown = this.multiples.controls.wrap
- .style('margin', 0)
- .selectAll('.control-group')
- .filter(function(d) {
- return d.option === 'selected_id';
- })
- .style('margin', 0)
- .style('display', 'block'); // firefox is being weird about inline-table
- participantDropdown.selectAll('*').style('display', 'inline-block');
- participantDropdown.selectAll('.wc-control-label').style('font-weight', 'bold');
- participantDropdown
- .selectAll('select')
- .style('margin-left', '3px')
- .style('width', null)
- .style('max-width', '10%')
- .on('change', function(d) {
- context.multiples.id = d3$1
- .select(this)
- .selectAll('option:checked')
- .text();
- clearSelected.call(context);
- context.selected_id = context.multiples.id;
- highlightSelected.call(context);
- smallMultiples.call(context);
- context.wrap.select('div.overlapNote').remove();
-
- //Trigger participantsSelected event
- context.participantsSelected = [context.selected_id];
- context.events.participantsSelected.data = context.participantsSelected;
- context.wrap.node().dispatchEvent(context.events.participantsSelected);
- });
- }
-
- function smallMultiples() {
- var _this = this;
-
- //Define participant data.
- this.multiples.data = this.initial_data.filter(function(d) {
- return d[_this.config.id_col] === _this.selected_id;
- });
-
- //Define small multiples.
- defineSmallMultiples.call(this);
-
- //Insert participant characteristics table.
- participantCharacteristics.call(this);
-
- //Add callbacks to small multiples.
- onLayout$1.call(this);
- onPreprocess$1.call(this);
- onResize.call(this);
-
- //Initialize small multiples.
- webcharts.multiply(this.multiples.chart, this.multiples.data, 'soe_measure', this.measures);
-
- //Update participant dropdown.
- updateParticipantDropdown.call(this);
- }
-
- function addLineEventListeners() {
- var _this = this;
-
- this.lines
- .on('mouseover', function(d) {
- clearHovered.call(_this);
- _this.hovered_id = d.values[0].values.raw[0][_this.config.id_col];
- if (_this.hovered_id !== _this.selected_id) highlightHovered.call(_this);
- })
- .on('mouseout', function(d) {
- clearHovered.call(_this);
- })
- .on('click', function(d) {
- clearHovered.call(_this);
- clearSelected.call(_this);
- _this.selected_id = d.values[0].values.raw[0][_this.config.id_col];
- highlightSelected.call(_this);
- smallMultiples.call(_this);
-
- //Trigger participantsSelected event
- _this.participantsSelected = [_this.selected_id];
- _this.events.participantsSelected.data = _this.participantsSelected;
- _this.wrap.node().dispatchEvent(_this.events.participantsSelected);
- });
- }
-
- function checkPointOverlap(d, chart) {
- // Get the position of the clicked point
- var click_x = d3
- .select(this)
- .select('circle')
- .attr('cx');
- var click_y = d3
- .select(this)
- .select('circle')
- .attr('cy');
- var click_r = d3
- .select(this)
- .select('circle')
- .attr('r');
- var click_id = d.values.raw[0][chart.config.id_col];
-
- // See if any other points overlap
- var overlap_ids = chart.points
- .filter(function(f) {
- var point_id = f.values.raw[0][chart.config.id_col];
- var point_x = d3
- .select(this)
- .select('circle')
- .attr('cx');
- var point_y = d3
- .select(this)
- .select('circle')
- .attr('cy');
- var distance_x2 = Math.pow(click_x - point_x, 2);
- var distance_y2 = Math.pow(click_y - point_y, 2);
- var distance = Math.sqrt(distance_x2 + distance_y2);
-
- var max_distance = click_r * 2;
- var overlap = distance <= max_distance;
- var diff_id = point_id != click_id;
- return diff_id & overlap;
- })
- .data()
- .map(function(d) {
- return d.values.raw[0][chart.config.id_col];
- });
-
- return overlap_ids;
- }
-
- function addOverlapNote(d, chart) {
- function showID(d) {
- //click an overlapping ID to see details for that participant
- var participantDropdown = chart.multiples.controls.wrap
- .style('margin', 0)
- .selectAll('.control-group')
- .filter(function(d) {
- return d.option === 'selected_id';
- })
- .select('select')
- .property('value', d);
-
- //participantDropdown.on("change")() // Can't quite get this to work, so copy/pasting for now ...
-
- var context = chart;
- chart.multiples.id = d;
- clearSelected.call(context);
- context.selected_id = context.multiples.id;
- highlightSelected.call(context);
- smallMultiples.call(context);
-
- //Trigger participantsSelected event
- context.participantsSelected = [context.selected_id];
- context.events.participantsSelected.data = context.participantsSelected;
- context.wrap.node().dispatchEvent(context.events.participantsSelected);
- }
-
- chart.wrap.select('div.overlapNote').remove();
-
- // check for overlapping points
- chart.overlap_ids = checkPointOverlap.call(this, d, chart);
-
- // If there are overlapping points, add a note in the details section.
-
- if (chart.overlap_ids.length) {
- var click_id = d.values.raw[0][chart.config.id_col];
- var overlap_div = chart.wrap
- .insert('div', 'div.multiples')
- .attr('class', 'overlapNote')
- .style('background-color', '#eee')
- .style('border', '1px solid #999')
- .style('padding', '0.5em')
- .style('border-radius', '0.2em')
- .style('margin', '0 0.1em');
-
- overlap_div
- .append('span')
- .html(
- 'Note: ' +
- chart.overlap_ids.length +
- (' point' +
- (chart.overlap_ids.length === 1 ? '' : 's') +
- ' overlap' +
- (chart.overlap_ids.length === 1 ? 's' : '') +
- ' the clicked point for ') +
- click_id +
- '. Click an ID for details: '
- );
- overlap_div
- .select('span.idLink')
- .datum(click_id)
- .style('color', 'blue')
- .style('text-decoration', 'underline')
- .style('cursor', 'pointer')
- .on('click', showID)
- .on('mouseover', function(d) {
- clearHovered.call(chart);
- chart.hovered_id = d;
- if (chart.hovered_id !== chart.selected_id) highlightHovered.call(chart);
- })
- .on('mouseout', function(d) {
- clearHovered.call(chart);
- });
- var overlap_ul = overlap_div
- .append('ul')
- .style('list-style', 'none')
- .style('padding', '0')
- .style('display', 'inline-block');
-
- overlap_ul
- .selectAll('li')
- .data(chart.overlap_ids)
- .enter()
- .append('li')
- .style('display', 'inline-block')
- .style('padding-right', '.5em')
- .attr('class', 'idLink')
- .style('color', 'blue')
- .style('text-decoration', 'underline')
- .style('cursor', 'pointer')
- .text(function(d) {
- return d;
- })
- .on('click', showID)
- .on('mouseover', function(d) {
- clearHovered.call(chart);
- chart.hovered_id = d;
- if (chart.hovered_id !== chart.selected_id) highlightHovered.call(chart);
- })
- .on('mouseout', function(d) {
- clearHovered.call(chart);
- });
- }
- }
-
- function addOverlapTitle(d, chart) {
- // check for overlapping points
- var overlap = checkPointOverlap.call(this, d, chart);
-
- // If there are overlapping points, add a note in the details section.
-
- if (overlap.length > 0) {
- var titleEl = d3.select(this).select('title');
- var currentTitle = titleEl.text();
- var hasOverlapNote = currentTitle.search('overlapping'); //minor hack ...
- if (hasOverlapNote == -1) {
- var newTitle =
- currentTitle + '\nNumber of overlapping point(s) = ' + overlap.length;
- titleEl.text(newTitle);
- }
- }
- }
-
- function addPointEventListeners() {
- var _this = this;
-
- var chart = this;
- this.points
- .on('mouseover', function(d) {
- addOverlapTitle.call(this, d, chart);
- clearHovered.call(chart);
- chart.hovered_id = d.values.raw[0][chart.config.id_col];
- if (chart.hovered_id !== chart.selected_id) highlightHovered.call(chart);
- })
- .on('mouseout', function(d) {
- clearHovered.call(_this);
- })
- .on('click', function(d) {
- clearHovered.call(chart);
- clearSelected.call(chart);
- chart.selected_id = d.values.raw[0][chart.config.id_col];
- highlightSelected.call(chart);
- smallMultiples.call(chart);
-
- //Trigger participantsSelected event
- chart.participantsSelected = [chart.selected_id];
- chart.events.participantsSelected.data = chart.participantsSelected;
- chart.wrap.node().dispatchEvent(chart.events.participantsSelected);
-
- //check for overlapping points
- addOverlapNote.call(this, d, chart);
- });
- }
-
- function addEventListeners() {
- addOverlayEventListener.call(this);
- addOverlayEventListener$1.call(this);
- addLineEventListeners.call(this);
- addPointEventListeners.call(this);
- }
-
- function addBoxPlot() {
- //Clear box plot.
- this.svg.select('g.boxplot').remove();
-
- //Customize box plot.
- var svg = this.svg;
- var results = this.current_data
- .map(function(d) {
- return +d.values.y;
- })
- .sort(d3$1.ascending);
- var height = this.plot_height;
- var width = 1;
- var domain = this.y_dom;
- var boxPlotWidth = 10;
- var boxColor = '#bbb';
- var boxInsideColor = 'white';
- var fmt = d3$1.format('.3r');
-
- //set up scales
- var x = d3$1.scale.linear().range([0, width]);
- var y = d3$1.scale.linear().range([height, 0]);
-
- {
- y.domain(domain);
- }
-
- var probs = [0.05, 0.25, 0.5, 0.75, 0.95];
- for (var i = 0; i < probs.length; i++) {
- probs[i] = d3$1.quantile(results, probs[i]);
- }
-
- var boxplot = this.svg
- .append('g')
- .attr('class', 'boxplot')
- .datum({
- values: results,
- probs: probs
- })
- .attr(
- 'transform',
- 'translate(' + (this.plot_width + this.config.margin.right / 2) + ',0)'
- );
-
- //draw rectangle from q1 to q3
- var box_x = x(0.5 - boxPlotWidth / 2);
- var box_width = x(0.5 + boxPlotWidth / 2) - x(0.5 - boxPlotWidth / 2);
- var box_y = y(probs[3]);
- var box_height = -y(probs[3]) + y(probs[1]);
-
- boxplot
- .append('rect')
- .attr('class', 'boxplot fill')
- .attr('x', box_x)
- .attr('width', box_width)
- .attr('y', box_y)
- .attr('height', box_height)
- .style('fill', boxColor);
-
- //draw dividing lines at median, 95% and 5%
- var iS = [0, 2, 4];
- var iSclass = ['', 'median', ''];
- var iSColor = [boxColor, boxInsideColor, boxColor];
- for (var i = 0; i < iS.length; i++) {
- boxplot
- .append('line')
- .attr('class', 'boxplot ' + iSclass[i])
- .attr('x1', x(0.5 - boxPlotWidth / 2))
- .attr('x2', x(0.5 + boxPlotWidth / 2))
- .attr('y1', y(probs[iS[i]]))
- .attr('y2', y(probs[iS[i]]))
- .style('fill', iSColor[i])
- .style('stroke', iSColor[i]);
- }
-
- //draw lines from 5% to 25% and from 75% to 95%
- var iS = [[0, 1], [3, 4]];
- for (var i = 0; i < iS.length; i++) {
- boxplot
- .append('line')
- .attr('class', 'boxplot')
- .attr('x1', x(0.5))
- .attr('x2', x(0.5))
- .attr('y1', y(probs[iS[i][0]]))
- .attr('y2', y(probs[iS[i][1]]))
- .style('stroke', boxColor);
- }
-
- boxplot
- .append('circle')
- .attr('class', 'boxplot mean')
- .attr('cx', x(0.5))
- .attr('cy', y(d3$1.mean(results)))
- .attr('r', x(boxPlotWidth / 3))
- .style('fill', boxInsideColor)
- .style('stroke', boxColor);
-
- boxplot
- .append('circle')
- .attr('class', 'boxplot mean')
- .attr('cx', x(0.5))
- .attr('cy', y(d3$1.mean(results)))
- .attr('r', x(boxPlotWidth / 6))
- .style('fill', boxColor)
- .style('stroke', 'None');
-
- boxplot.append('title').text(function(d) {
- var tooltip =
- 'N = ' +
- d.values.length +
- '\n' +
- 'Min = ' +
- d3$1.min(d.values) +
- '\n' +
- '5th % = ' +
- fmt(d3$1.quantile(d.values, 0.05)).replace(/^ */, '') +
- '\n' +
- 'Q1 = ' +
- fmt(d3$1.quantile(d.values, 0.25)).replace(/^ */, '') +
- '\n' +
- 'Median = ' +
- fmt(d3$1.median(d.values)).replace(/^ */, '') +
- '\n' +
- 'Q3 = ' +
- fmt(d3$1.quantile(d.values, 0.75)).replace(/^ */, '') +
- '\n' +
- '95th % = ' +
- fmt(d3$1.quantile(d.values, 0.95)).replace(/^ */, '') +
- '\n' +
- 'Max = ' +
- d3$1.max(d.values) +
- '\n' +
- 'Mean = ' +
- fmt(d3$1.mean(d.values)).replace(/^ */, '') +
- '\n' +
- 'StDev = ' +
- fmt(d3$1.deviation(d.values)).replace(/^ */, '');
- return tooltip;
- });
- }
-
- function onResize$1() {
- //Attach mark groups to central chart object.
- attachMarks.call(this);
-
- //Maintain mark highlighting.
- maintainHighlight.call(this);
-
- //Draw normal range.
- drawNormalRange.call(this);
-
- //Add event listeners to lines, points, and overlay.
- addEventListeners.call(this);
-
- //Draw a marginal box plot.
- addBoxPlot.call(this);
-
- //Rotate tick marks to prevent text overlap.
- adjustTicks.call(this);
- }
-
- function onDestroy() {}
-
- var callbacks = {
- onInit: onInit,
- onLayout: onLayout,
- onPreprocess: onPreprocess,
- onDatatransform: onDatatransform,
- onDraw: onDraw,
- onResize: onResize$1,
- onDestroy: onDestroy
- };
-
- function safetyOutlierExplorer(element, settings) {
- //Merge user settings with default settings.
- var mergedSettings = Object.assign({}, configuration.settings, settings);
-
- //Sync options within settings object, e.g. data mappings.
- var syncedSettings = configuration.syncSettings(mergedSettings);
-
- //Sync control inputs with with settings object.
- var syncedControlInputs = configuration.syncControlInputs(
- configuration.controlInputs(),
- syncedSettings
- );
-
- //Define controls.
- var controls = webcharts.createControls(element, {
- location: 'top',
- inputs: syncedControlInputs
- });
-
- //Define chart.
- var chart = webcharts.createChart(element, syncedSettings, controls);
- chart.config.marks.forEach(function(mark) {
- mark.attributes = mark.attributes || {};
- mark.attributes['clip-path'] = 'url(#' + chart.id + ')';
- });
-
- //Attach callbacks to chart.
- for (var callback in callbacks) {
- chart.on(callback.substring(2).toLowerCase(), callbacks[callback]);
- }
- return chart;
- }
-
- return safetyOutlierExplorer;
-});
diff --git a/inst/htmlwidgets/lib/safety-results-over-time-2.3.3/safetyResultsOverTime.js b/inst/htmlwidgets/lib/safety-results-over-time-2.3.3/safetyResultsOverTime.js
deleted file mode 100644
index 0a21b6ac..00000000
--- a/inst/htmlwidgets/lib/safety-results-over-time-2.3.3/safetyResultsOverTime.js
+++ /dev/null
@@ -1,1866 +0,0 @@
-(function(global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined'
- ? (module.exports = factory(require('d3'), require('webcharts')))
- : typeof define === 'function' && define.amd
- ? define(['d3', 'webcharts'], factory)
- : ((global = global || self),
- (global.safetyResultsOverTime = factory(global.d3, global.webCharts)));
-})(this, function(d3, webcharts) {
- 'use strict';
-
- if (typeof Object.assign != 'function') {
- Object.defineProperty(Object, 'assign', {
- value: function assign(target, varArgs) {
- if (target == null) {
- // TypeError if undefined or null
- throw new TypeError('Cannot convert undefined or null to object');
- }
-
- var to = Object(target);
-
- for (var index = 1; index < arguments.length; index++) {
- var nextSource = arguments[index];
-
- if (nextSource != null) {
- // Skip over if undefined or null
- for (var nextKey in nextSource) {
- // Avoid bugs when hasOwnProperty is shadowed
- if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
- to[nextKey] = nextSource[nextKey];
- }
- }
- }
- }
-
- return to;
- },
- writable: true,
- configurable: true
- });
- }
-
- if (!Array.prototype.find) {
- Object.defineProperty(Array.prototype, 'find', {
- value: function value(predicate) {
- // 1. Let O be ? ToObject(this value).
- if (this == null) {
- throw new TypeError('"this" is null or not defined');
- }
-
- var o = Object(this);
-
- // 2. Let len be ? ToLength(? Get(O, 'length')).
- var len = o.length >>> 0;
-
- // 3. If IsCallable(predicate) is false, throw a TypeError exception.
- if (typeof predicate !== 'function') {
- throw new TypeError('predicate must be a function');
- }
-
- // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
- var thisArg = arguments[1];
-
- // 5. Let k be 0.
- var k = 0;
-
- // 6. Repeat, while k < len
- while (k < len) {
- // a. Let Pk be ! ToString(k).
- // b. Let kValue be ? Get(O, Pk).
- // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)).
- // d. If testResult is true, return kValue.
- var kValue = o[k];
- if (predicate.call(thisArg, kValue, k, o)) {
- return kValue;
- }
- // e. Increase k by 1.
- k++;
- }
-
- // 7. Return undefined.
- return undefined;
- }
- });
- }
-
- if (!Array.prototype.findIndex) {
- Object.defineProperty(Array.prototype, 'findIndex', {
- value: function value(predicate) {
- // 1. Let O be ? ToObject(this value).
- if (this == null) {
- throw new TypeError('"this" is null or not defined');
- }
-
- var o = Object(this);
-
- // 2. Let len be ? ToLength(? Get(O, "length")).
- var len = o.length >>> 0;
-
- // 3. If IsCallable(predicate) is false, throw a TypeError exception.
- if (typeof predicate !== 'function') {
- throw new TypeError('predicate must be a function');
- }
-
- // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
- var thisArg = arguments[1];
-
- // 5. Let k be 0.
- var k = 0;
-
- // 6. Repeat, while k < len
- while (k < len) {
- // a. Let Pk be ! ToString(k).
- // b. Let kValue be ? Get(O, Pk).
- // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)).
- // d. If testResult is true, return k.
- var kValue = o[k];
- if (predicate.call(thisArg, kValue, k, o)) {
- return k;
- }
- // e. Increase k by 1.
- k++;
- }
-
- // 7. Return -1.
- return -1;
- }
- });
- }
-
- Math.log10 =
- Math.log10 ||
- function(x) {
- return Math.log(x) * Math.LOG10E;
- };
-
- var _typeof =
- typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol'
- ? function(obj) {
- return typeof obj;
- }
- : function(obj) {
- return obj &&
- typeof Symbol === 'function' &&
- obj.constructor === Symbol &&
- obj !== Symbol.prototype
- ? 'symbol'
- : typeof obj;
- };
-
- var hasOwnProperty = Object.prototype.hasOwnProperty;
- var propIsEnumerable = Object.prototype.propertyIsEnumerable;
-
- function toObject(val) {
- if (val === null || val === undefined) {
- throw new TypeError('Cannot convert undefined or null to object');
- }
-
- return Object(val);
- }
-
- function isObj(x) {
- var type = typeof x === 'undefined' ? 'undefined' : _typeof(x);
- return x !== null && (type === 'object' || type === 'function');
- }
-
- function assignKey(to, from, key) {
- var val = from[key];
-
- if (val === undefined) {
- return;
- }
-
- if (hasOwnProperty.call(to, key)) {
- if (to[key] === undefined) {
- throw new TypeError('Cannot convert undefined or null to object (' + key + ')');
- }
- }
-
- if (!hasOwnProperty.call(to, key) || !isObj(val)) to[key] = val;
- else if (val instanceof Array) to[key] = from[key];
- // figure out how to merge arrays without converting them into objects
- else to[key] = assign(Object(to[key]), from[key]);
- }
-
- function assign(to, from) {
- if (to === from) {
- return to;
- }
-
- from = Object(from);
-
- for (var key in from) {
- if (hasOwnProperty.call(from, key)) {
- assignKey(to, from, key);
- }
- }
-
- if (Object.getOwnPropertySymbols) {
- var symbols = Object.getOwnPropertySymbols(from);
-
- for (var i = 0; i < symbols.length; i++) {
- if (propIsEnumerable.call(from, symbols[i])) {
- assignKey(to, from, symbols[i]);
- }
- }
- }
-
- return to;
- }
-
- function merge(target) {
- target = toObject(target);
-
- for (var s = 1; s < arguments.length; s++) {
- assign(target, arguments[s]);
- }
-
- return target;
- }
-
- function rendererSettings() {
- return {
- id_col: 'USUBJID',
- time_settings: {
- value_col: 'VISIT',
- label: 'Visit',
- order_col: 'VISITNUM',
- order: null,
- rotate_tick_labels: true,
- vertical_space: 100
- },
- measure_col: 'TEST',
- value_col: 'STRESN',
- unit_col: 'STRESU',
- normal_col_low: 'STNRLO',
- normal_col_high: 'STNRHI',
- start_value: null,
- filters: null,
- groups: null,
- color_by: null,
- boxplots: true,
- outliers: true,
- violins: false,
- missingValues: ['', 'NA', 'N/A'],
- visits_without_data: false,
- unscheduled_visits: false,
- unscheduled_visit_pattern: '/unscheduled|early termination/i',
- unscheduled_visit_values: null // takes precedence over unscheduled_visit_pattern
- };
- }
-
- function webchartsSettings() {
- return {
- x: {
- column: null, // set in syncSettings()
- type: 'ordinal',
- label: null,
- behavior: 'flex',
- sort: 'alphabetical-ascending',
- tickAttr: null
- },
- y: {
- column: null, // set in syncSettings()
- type: 'linear',
- label: null,
- behavior: 'flex',
- stat: 'mean',
- format: null // set in ./onPreprocess/setYprecision()
- },
- marks: [
- {
- type: 'line',
- per: null, // set in syncSettings()
- attributes: {
- 'stroke-width': 2,
- 'stroke-opacity': 1,
- display: 'none'
- }
- },
- {
- type: 'circle',
- per: null, // set in syncSettings()
- attributes: {
- stroke: 'black',
- 'stroke-opacity': 0,
- 'fill-opacity': 0
- },
- values: {
- srot_outlier: [true]
- },
- radius: null, // set in syncSettings()
- tooltip: null, // set in syncSettings()
- hidden: true
- },
- {
- type: 'circle',
- per: null, // set in syncSettings()
- attributes: {
- stroke: 'black',
- 'stroke-opacity': 1,
- 'fill-opacity': 1
- },
- values: {
- srot_outlier: [true]
- },
- radius: 1.75,
- tooltip: null, // set in syncSettings()
- hidden: false
- }
- ],
- legend: {
- mark: 'square'
- },
- color_by: null, // set in syncSettings()
- resizable: true,
- gridlines: 'y',
- aspect: 3
- };
- }
-
- function syncSettings(settings) {
- //x-axis
- settings.x.column = settings.time_settings.value_col;
- settings.x.label = settings.time_settings.label;
- settings.x.behavior = settings.visits_without_data ? 'raw' : 'flex';
-
- //y-axis
- settings.y.column = settings.value_col;
-
- //handle a string arguments to array settings
- var array_settings = ['filters', 'groups', 'missingValues'];
- array_settings.forEach(function(s) {
- if (!(settings[s] instanceof Array))
- settings[s] = typeof settings[s] === 'string' ? [settings[s]] : [];
- });
-
- //stratification
- var defaultGroup = { value_col: 'srot_none', label: 'None' };
- if (!(settings.groups instanceof Array && settings.groups.length))
- settings.groups = [defaultGroup];
- else
- settings.groups = [defaultGroup].concat(
- settings.groups.map(function(group) {
- return {
- value_col: group.value_col || group,
- label: group.label || group.value_col || group
- };
- })
- );
-
- //Remove duplicate values.
- settings.groups = d3
- .set(
- settings.groups.map(function(group) {
- return group.value_col;
- })
- )
- .values()
- .map(function(value) {
- return {
- value_col: value,
- label: settings.groups.find(function(group) {
- return group.value_col === value;
- }).label
- };
- });
-
- //Set initial group-by variable.
- settings.color_by = settings.color_by
- ? settings.color_by
- : settings.groups.length > 1
- ? settings.groups[1].value_col
- : defaultGroup.value_col;
-
- //Set initial group-by label.
- settings.legend.label = settings.groups.find(function(group) {
- return group.value_col === settings.color_by;
- }).label;
-
- //marks
- var lines = settings.marks.find(function(mark) {
- return mark.type === 'line';
- });
- var hiddenOutliers = settings.marks.find(function(mark) {
- return mark.type === 'circle' && mark.hidden;
- });
- var visibleOutliers = settings.marks.find(function(mark) {
- return mark.type === 'circle' && !mark.hidden;
- });
- lines.per = [settings.color_by];
- hiddenOutliers.radius = visibleOutliers.radius * 4;
- settings.marks
- .filter(function(mark) {
- return mark.type === 'circle';
- })
- .forEach(function(mark) {
- mark.per = [settings.id_col, settings.time_settings.value_col, settings.value_col];
- mark.tooltip =
- '[' +
- settings.id_col +
- '] at [' +
- settings.x.column +
- ']: [' +
- settings.value_col +
- ']';
- });
-
- //miscellany
- settings.margin = settings.margin || { bottom: settings.time_settings.vertical_space };
-
- //Convert unscheduled_visit_pattern from string to regular expression.
- if (
- typeof settings.unscheduled_visit_pattern === 'string' &&
- settings.unscheduled_visit_pattern !== ''
- ) {
- var flags = settings.unscheduled_visit_pattern.replace(/.*?\/([gimy]*)$/, '$1'),
- pattern = settings.unscheduled_visit_pattern.replace(
- new RegExp('^/(.*?)/' + flags + '$'),
- '$1'
- );
- settings.unscheduled_visit_regex = new RegExp(pattern, flags);
- }
-
- return settings;
- }
-
- function controlInputs() {
- return [
- {
- type: 'subsetter',
- label: 'Measure',
- value_col: 'srot_measure', // set in syncControlInputs()
- start: null // set in ../callbacks/onInit/setInitialMeasure.js
- },
- {
- type: 'dropdown',
- label: 'Group by',
- options: ['marks.0.per.0', 'color_by'],
- start: null, // set in ./syncControlInputs.js
- values: null, // set in ./syncControlInputs.js
- require: true
- },
- {
- type: 'number',
- label: 'Lower',
- grouping: 'y-axis',
- option: 'y.domain[0]',
- require: true
- },
- {
- type: 'number',
- label: 'Upper',
- grouping: 'y-axis',
- option: 'y.domain[1]',
- require: true
- },
- {
- type: 'radio',
- option: 'y.type',
- grouping: 'y-axis',
- values: ['linear', 'log'],
- label: 'Scale'
- },
- {
- type: 'checkbox',
- inline: true,
- option: 'visits_without_data',
- label: 'Visits without data'
- },
- {
- type: 'checkbox',
- inline: true,
- option: 'unscheduled_visits',
- label: 'Unscheduled visits'
- },
- { type: 'checkbox', inline: true, option: 'boxplots', label: 'Box plots' },
- { type: 'checkbox', inline: true, option: 'violins', label: 'Violin plots' },
- { type: 'checkbox', inline: true, option: 'outliers', label: 'Outliers' }
- ];
- }
-
- function syncControlInputs(controlInputs, settings) {
- //Sync group control.
- var groupControl = controlInputs.find(function(controlInput) {
- return controlInput.label === 'Group by';
- });
- groupControl.start = settings.groups.find(function(group) {
- return group.value_col === settings.color_by;
- }).label;
- groupControl.values = settings.groups.map(function(group) {
- return group.label;
- });
-
- //Add custom filters to control inputs.
- if (settings.filters) {
- settings.filters.reverse().forEach(function(filter) {
- var thisFilter = {
- type: 'subsetter',
- value_col: filter.value_col ? filter.value_col : filter,
- label: filter.label
- ? filter.label
- : filter.value_col
- ? filter.value_col
- : filter,
- description: 'filter'
- };
-
- //add the filter to the control inputs (as long as it's not already there)
- var current_value_cols = controlInputs
- .filter(function(f) {
- return f.type == 'subsetter';
- })
- .map(function(m) {
- return m.value_col;
- });
- if (current_value_cols.indexOf(thisFilter.value_col) == -1)
- controlInputs.splice(1, 0, thisFilter);
- });
- }
-
- //Remove unscheduled visit control if unscheduled visit pattern is unscpecified.
- if (!settings.unscheduled_visit_regex)
- controlInputs.splice(
- controlInputs
- .map(function(controlInput) {
- return controlInput.label;
- })
- .indexOf('Unscheduled visits'),
- 1
- );
-
- return controlInputs;
- }
-
- var configuration = {
- rendererSettings: rendererSettings,
- webchartsSettings: webchartsSettings,
- defaultSettings: Object.assign({}, rendererSettings(), webchartsSettings()),
- syncSettings: syncSettings,
- controlInputs: controlInputs,
- syncControlInputs: syncControlInputs
- };
-
- function countParticipants() {
- var _this = this;
-
- this.populationCount = d3
- .set(
- this.raw_data.map(function(d) {
- return d[_this.config.id_col];
- })
- )
- .values().length;
- }
-
- function cleanData() {
- var _this = this;
-
- //Remove missing and non-numeric data.
- var preclean = this.raw_data,
- clean = this.raw_data.filter(function(d) {
- return /^-?[0-9.]+$/.test(d[_this.config.value_col]);
- }),
- nPreclean = preclean.length,
- nClean = clean.length,
- nRemoved = nPreclean - nClean;
-
- //Warn user of removed records.
- if (nRemoved > 0)
- console.warn(
- nRemoved +
- ' missing or non-numeric result' +
- (nRemoved > 1 ? 's have' : ' has') +
- ' been removed.'
- );
- this.initial_data = clean;
- this.raw_data = clean;
- }
-
- function addVariables() {
- var _this = this;
-
- this.raw_data.forEach(function(d) {
- //Convert results to numeric
- d[_this.config.y.column] = parseFloat(d[_this.config.y.column]);
-
- //Concatenate unit to measure if provided.
- d.srot_measure = d.hasOwnProperty(_this.config.unit_col)
- ? d[_this.config.measure_col] + ' (' + d[_this.config.unit_col] + ')'
- : d[_this.config.measure_col];
-
- //Add placeholder variable for non-grouped comparisons.
- d.srot_none = 'All Participants';
-
- //Add placeholder variable for outliers.
- d.srot_outlier = null;
- });
- this.variables = Object.keys(this.raw_data[0]);
- }
-
- function defineVisitOrder() {
- var _this = this;
-
- var visits = void 0,
- visitOrder = void 0;
-
- //Given an ordering variable sort a unique set of visits by the ordering variable.
- if (
- this.config.time_settings.order_col &&
- this.raw_data[0].hasOwnProperty(this.config.time_settings.order_col)
- ) {
- //Define a unique set of visits with visit order concatenated.
- visits = d3
- .set(
- this.raw_data.map(function(d) {
- return (
- d[_this.config.time_settings.order_col] +
- '|' +
- d[_this.config.time_settings.value_col]
- );
- })
- )
- .values();
-
- //Sort visits.
- visitOrder = visits
- .sort(function(a, b) {
- var aOrder = a.split('|')[0],
- bOrder = b.split('|')[0],
- diff = +aOrder - +bOrder;
- return diff ? diff : d3.ascending(a, b);
- })
- .map(function(visit) {
- return visit.split('|')[1];
- });
- } else {
- //Otherwise sort a unique set of visits alphanumerically.
- //Define a unique set of visits.
- visits = d3
- .set(
- this.raw_data.map(function(d) {
- return d[_this.config.time_settings.value_col];
- })
- )
- .values();
-
- //Sort visits;
- visitOrder = visits.sort();
- }
-
- //Set x-axis domain.
- if (this.config.time_settings.order) {
- //If a visit order is specified, use it and concatenate any unspecified visits at the end.
- this.config.x.order = this.config.time_settings.order.concat(
- visitOrder.filter(function(visit) {
- return _this.config.time_settings.order.indexOf(visit) < 0;
- })
- );
- }
- //Otherwise use data-driven visit order.
- else this.config.x.order = visitOrder;
- }
-
- function checkFilters() {
- var _this = this;
-
- this.controls.config.inputs = this.controls.config.inputs.filter(function(input) {
- if (input.type != 'subsetter') {
- return true;
- } else if (!_this.raw_data[0].hasOwnProperty(input.value_col)) {
- console.warn(
- 'The [ ' +
- input.label +
- ' ] filter has been removed because the variable does not exist.'
- );
- } else {
- var levels = d3
- .set(
- _this.raw_data.map(function(d) {
- return d[input.value_col];
- })
- )
- .values();
-
- if (levels.length === 1)
- console.warn(
- 'The [ ' +
- input.label +
- ' ] filter has been removed because the variable has only one level.'
- );
-
- return levels.length > 1;
- }
- });
- }
-
- function checkGroupByVariables() {
- var _this = this;
-
- var groupByInput = this.controls.config.inputs.find(function(input) {
- return input.label === 'Group by';
- });
- this.config.groups = this.config.groups.filter(function(group) {
- var groupByExists = _this.variables.indexOf(group.value_col) > -1;
- if (!groupByExists)
- console.warn(
- 'The [ ' +
- group.label +
- ' ] group-by option has been removed because the variable does not exist.'
- );
- return groupByExists;
- });
- groupByInput.values = this.config.groups.map(function(group) {
- return group.label;
- });
- }
-
- function defineMeasureSet() {
- var _this = this;
-
- this.measures = d3
- .set(
- this.initial_data.map(function(d) {
- return d[_this.config.measure_col];
- })
- )
- .values()
- .sort();
- this.srot_measures = d3
- .set(
- this.initial_data.map(function(d) {
- return d.srot_measure;
- })
- )
- .values()
- .sort();
- }
-
- function setInitialMeasure() {
- var measureInput = this.controls.config.inputs.find(function(input) {
- return input.label === 'Measure';
- });
- if (
- this.config.start_value &&
- this.srot_measures.indexOf(this.config.start_value) < 0 &&
- this.measures.indexOf(this.config.start_value) < 0
- ) {
- measureInput.start = this.srot_measures[0];
- console.warn(
- this.config.start_value +
- ' is an invalid measure. Defaulting to ' +
- measureInput.start +
- '.'
- );
- } else if (
- this.config.start_value &&
- this.srot_measures.indexOf(this.config.start_value) < 0
- ) {
- measureInput.start = this.srot_measures[this.measures.indexOf(this.config.start_value)];
- console.warn(
- this.config.start_value +
- ' is missing the units value. Defaulting to ' +
- measureInput.start +
- '.'
- );
- } else measureInput.start = this.config.start_value || this.srot_measures[0];
- }
-
- function onInit() {
- // 1. Count total participants prior to data cleaning.
- countParticipants.call(this);
-
- // 2. Drop missing values and remove measures with any non-numeric results.
- cleanData.call(this);
-
- // 3a Define additional variables.
- addVariables.call(this);
-
- // 3b Define ordered x-axis domain with visit order variable.
- defineVisitOrder.call(this);
-
- // 3c Remove filters for nonexistent or single-level variables.
- checkFilters.call(this);
-
- // 3d Remove group-by options for nonexistent variables.
- checkGroupByVariables.call(this);
-
- // 4. Define set of measures.
- defineMeasureSet.call(this);
-
- // 5. Set the start value of the Measure filter.
- setInitialMeasure.call(this);
- }
-
- function classControlGroups() {
- var checkboxOffset = 0;
- this.controls.wrap
- .style('position', 'relative')
- .selectAll('.control-group')
- .each(function(d, i) {
- var controlGroup = d3.select(this);
- controlGroup.classed(
- d.type.toLowerCase().replace(' ', '-') +
- ' ' +
- d.label.toLowerCase().replace(' ', '-'),
- true
- );
-
- //Add y-axis class to group y-axis controls.
- if (d.grouping) controlGroup.classed(d.grouping, true);
-
- //Float all checkboxes right.
- if (d.type === 'checkbox') {
- controlGroup.style({
- position: 'absolute',
- top: checkboxOffset + 'px',
- right: 0,
- margin: '0'
- });
- checkboxOffset += controlGroup.node().offsetHeight;
- }
- });
- }
-
- function customizeGroupByControl() {
- var _this = this;
-
- var context = this;
-
- var groupControl = this.controls.wrap.selectAll('.control-group.dropdown.group-by');
- if (groupControl.datum().values.length === 1) groupControl.style('display', 'none');
- else
- groupControl
- .selectAll('select')
- .on('change', function(d) {
- var label = d3
- .select(this)
- .selectAll('option:checked')
- .text();
- var value_col = context.config.groups.find(function(group) {
- return group.label === label;
- }).value_col;
- context.config.marks[0].per[0] = value_col;
- context.config.color_by = value_col;
- context.config.legend.label = label;
- context.draw();
- })
- .selectAll('option')
- .property('selected', function(d) {
- return d === _this.config.legend.label;
- });
- }
-
- function addYDomainResetButton() {
- var context = this,
- resetContainer = this.controls.wrap
- .insert('div', '.lower')
- .classed('control-group y-axis', true)
- .datum({
- type: 'button',
- option: 'y.domain',
- label: 'Limits'
- }),
- resetLabel = resetContainer
- .append('span')
- .attr('class', 'wc-control-label')
- .text('Limits'),
- resetButton = resetContainer
- .append('button')
- .style('padding', '0px 5px')
- .text('Reset')
- .on('click', function() {
- var measure_data = context.raw_data.filter(function(d) {
- return d.srot_measure === context.currentMeasure;
- });
- context.config.y.domain = d3.extent(measure_data, function(d) {
- return +d[context.config.value_col];
- }); //reset axis to full range
-
- context.controls.wrap
- .selectAll('.control-group')
- .filter(function(f) {
- return f.option === 'y.domain[0]';
- })
- .select('input')
- .property('value', context.config.y.domain[0]);
-
- context.controls.wrap
- .selectAll('.control-group')
- .filter(function(f) {
- return f.option === 'y.domain[1]';
- })
- .select('input')
- .property('value', context.config.y.domain[1]);
-
- context.draw();
- });
- }
-
- function groupYAxisControls() {
- //Define a container in which to place y-axis controls.
- var grouping = this.controls.wrap
- .insert('div', '.y-axis')
- .style({
- display: 'inline-block',
- 'margin-right': '5px'
- })
- .append('fieldset')
- .style('padding', '0px 2px');
- grouping.append('legend').text('Y-axis');
-
- //Move each y-axis control into container.
- this.controls.wrap.selectAll('.y-axis').each(function(d) {
- this.style.marginTop = '0px';
- this.style.marginRight = '2px';
- this.style.marginBottom = '2px';
- this.style.marginLeft = '2px';
- grouping.node().appendChild(this);
-
- //Radio buttons sit too low.
- if (d.option === 'y.type')
- d3.select(this)
- .selectAll('input[type=radio]')
- .style({
- top: '-.1em'
- });
- });
- }
-
- function addPopulationCountContainer() {
- this.populationCountContainer = this.controls.wrap
- .append('div')
- .classed('population-count', true)
- .style('font-style', 'italic');
- }
-
- function addBorderAboveChart() {
- this.wrap.style('border-top', '1px solid #ccc');
- }
-
- function onLayout() {
- classControlGroups.call(this);
- customizeGroupByControl.call(this);
- addYDomainResetButton.call(this);
- groupYAxisControls.call(this);
- addPopulationCountContainer.call(this);
- addBorderAboveChart.call(this);
- }
-
- function getCurrentMeasure() {
- this.previousMeasure = this.currentMeasure;
- this.currentMeasure = this.controls.wrap
- .selectAll('.control-group')
- .filter(function(d) {
- return d.value_col && d.value_col === 'srot_measure';
- })
- .selectAll('option:checked')
- .text();
- this.config.y.label = this.currentMeasure;
- this.previousYAxis = this.currentYAxis;
- this.currentYAxis = this.config.y.type;
- }
-
- function defineMeasureData() {
- var _this = this;
-
- //Filter raw data on selected measure.
- this.measure_data = this.initial_data.filter(function(d) {
- return d.srot_measure === _this.currentMeasure;
- });
-
- //Remove nonpositive results given log y-axis.
- this.controls.wrap.select('.non-positive-results').remove();
- if (this.config.y.type === 'log') {
- var nResults = this.measure_data.length;
- this.measure_data = this.measure_data.filter(function(d) {
- return +d[_this.config.value_col] > 0;
- });
- var nonPositiveResults = nResults - this.measure_data.length;
- if (nonPositiveResults > 0)
- this.controls.wrap
- .selectAll('.axis-type .radio')
- .filter(function() {
- return (
- d3
- .select(this)
- .select('input')
- .attr('value') === 'log'
- );
- })
- .append('small')
- .classed('non-positive-results', true)
- .text(
- nonPositiveResults +
- ' nonpositive result' +
- (nonPositiveResults > 1 ? 's' : '') +
- ' removed.'
- );
- }
- this.raw_data = this.measure_data;
-
- //Apply filter to measure data.
- this.filtered_measure_data = this.measure_data;
- this.filters.forEach(function(filter) {
- _this.filtered_measure_data = _this.filtered_measure_data.filter(function(d) {
- return Array.isArray(filter.val)
- ? filter.val.indexOf(d[filter.col]) > -1
- : filter.val === d[filter.col] || filter.val === 'All';
- });
- });
-
- //Nest data and calculate summary statistics for each visit-group combination.
- this.nested_measure_data = d3
- .nest()
- .key(function(d) {
- return d[_this.config.x.column];
- })
- .key(function(d) {
- return d[_this.config.color_by];
- })
- .rollup(function(d) {
- var results = {
- values: d
- .map(function(m) {
- return +m[_this.config.y.column];
- })
- .sort(d3.ascending),
- n: d.length
- };
-
- //Calculate summary statistics.
- [
- 'min',
- ['quantile', 0.05],
- ['quantile', 0.25],
- 'median',
- ['quantile', 0.75],
- ['quantile', 0.95],
- 'max',
- 'mean',
- 'deviation'
- ].forEach(function(item) {
- var fx = Array.isArray(item) ? item[0] : item;
- var stat = Array.isArray(item) ? '' + fx.substring(0, 1) + item[1] * 100 : fx;
- results[stat] = Array.isArray(item)
- ? d3[fx](results.values, item[1])
- : d3[fx](results.values);
- });
-
- return results;
- })
- .entries(this.filtered_measure_data);
- }
-
- function flagOutliers() {
- var _this = this;
-
- this.quantileMap = new Map();
- this.nested_measure_data.forEach(function(visit) {
- visit.values.forEach(function(group) {
- _this.quantileMap.set(
- visit.key + '|' + group.key, // key
- [group.values.q5, group.values.q95] // value
- );
- });
- });
- this.filtered_measure_data.forEach(function(d) {
- var quantiles = _this.quantileMap.get(
- d[_this.config.x.column] + '|' + d[_this.config.color_by]
- );
- d.srot_outlier = _this.config.outliers
- ? d[_this.config.y.column] < quantiles[0] || quantiles[1] < d[_this.config.y.column]
- : false;
- });
- }
-
- function removeVisitsWithoutData() {
- var _this = this;
-
- if (!this.config.visits_without_data)
- this.config.x.domain = this.config.x.domain.filter(function(visit) {
- return (
- d3
- .set(
- _this.filtered_measure_data.map(function(d) {
- return d[_this.config.time_settings.value_col];
- })
- )
- .values()
- .indexOf(visit) > -1
- );
- });
- }
-
- function removeUnscheduledVisits() {
- var _this = this;
-
- if (!this.config.unscheduled_visits) {
- if (this.config.unscheduled_visit_values)
- this.config.x.domain = this.config.x.domain.filter(function(visit) {
- return _this.config.unscheduled_visit_values.indexOf(visit) < 0;
- });
- else if (this.config.unscheduled_visit_regex)
- this.config.x.domain = this.config.x.domain.filter(function(visit) {
- return !_this.config.unscheduled_visit_regex.test(visit);
- });
-
- //Remove unscheduled visits from raw data.
- this.raw_data = this.raw_data.filter(function(d) {
- return _this.config.x.domain.indexOf(d[_this.config.time_settings.value_col]) > -1;
- });
- }
- }
-
- function setXdomain() {
- this.config.x.domain = this.config.x.order;
- removeVisitsWithoutData.call(this);
- removeUnscheduledVisits.call(this);
- }
-
- function setYdomain() {
- var _this = this;
-
- //Define y-domain.
- if (
- this.currentMeasure !== this.previousMeasure ||
- this.currentYAxis !== this.previousYAxis
- )
- this.config.y.domain = d3.extent(
- this.measure_data.map(function(d) {
- return +d[_this.config.y.column];
- })
- );
- else if (this.config.y.domain[0] > this.config.y.domain[1])
- // new measure
- this.config.y.domain.reverse();
- else if (this.config.y.domain[0] === this.config.y.domain[1])
- // invalid domain
- this.config.y.domain = this.config.y.domain.map(function(d, i) {
- return i === 0 ? d - d * 0.01 : d + d * 0.01;
- }); // domain with zero range
- }
-
- function setYprecision() {
- var _this = this;
-
- //Calculate range of current measure and the log10 of the range to choose an appropriate precision.
- this.config.y.range = this.config.y.domain[1] - this.config.y.domain[0];
- this.config.y.log10range = Math.log10(this.config.y.range);
- this.config.y.roundedLog10range = Math.round(this.config.y.log10range);
- this.config.y.precision1 = -1 * (this.config.y.roundedLog10range - 1);
- this.config.y.precision2 = -1 * (this.config.y.roundedLog10range - 2);
-
- //Define the format of the y-axis tick labels and y-domain controls.
- this.config.y.precision = this.config.y.log10range > 0.5 ? 0 : this.config.y.precision1;
- this.config.y.format =
- this.config.y.log10range > 0.5 ? '1f' : '.' + this.config.y.precision1 + 'f';
- this.config.y.d3_format = d3.format(this.config.y.format);
- this.config.y.formatted_domain = this.config.y.domain.map(function(d) {
- return _this.config.y.d3_format(d);
- });
-
- //Define the bin format: one less than the y-axis format.
- this.config.y.format1 =
- this.config.y.log10range > 5 ? '1f' : '.' + this.config.y.precision2 + 'f';
- this.config.y.d3_format1 = d3.format(this.config.y.format1);
- }
-
- function updateYaxisResetButton() {
- //Update tooltip of y-axis domain reset button.
- if (this.currentMeasure !== this.previousMeasure)
- this.controls.wrap
- .selectAll('.y-axis')
- .property(
- 'title',
- 'Initial Limits: [' +
- this.config.y.domain[0] +
- ' - ' +
- this.config.y.domain[1] +
- ']'
- );
- }
-
- function updateYaxisLimitControls() {
- var _this = this;
-
- //Update y-axis limit controls.
- var step = Math.pow(10, -this.config.y.precision);
- var yDomain = this.config.y.domain.map(function(limit) {
- return _this.config.y.d3_format(limit);
- });
- this.controls.wrap
- .selectAll('.control-group')
- .filter(function(f) {
- return f.option === 'y.domain[0]';
- })
- .select('input')
- .attr('step', step)
- .property('value', yDomain[0]);
- this.controls.wrap
- .selectAll('.control-group')
- .filter(function(f) {
- return f.option === 'y.domain[1]';
- })
- .select('input')
- .attr('step', step)
- .property('value', yDomain[1]);
- }
-
- function onPreprocess() {
- // 1. Capture currently selected measure.
- getCurrentMeasure.call(this);
-
- // 2. Filter data on currently selected measure.
- defineMeasureData.call(this);
-
- // 3a Flag outliers with quantiles calculated in defineMeasureData().
- flagOutliers.call(this);
-
- // 3a Set x-domain given current visit settings.
- setXdomain.call(this);
-
- // 3b Set y-domain given currently selected measure.
- setYdomain.call(this);
-
- // 4a Define precision of measure.
- setYprecision.call(this);
-
- // 4b Update y-axis reset button when measure changes.
- updateYaxisResetButton.call(this);
-
- // 4c Update y-axis limit controls to match y-axis domain.
- updateYaxisLimitControls.call(this);
- }
-
- function onDatatransform() {}
-
- function updateParticipantCount() {
- var _this = this;
-
- this.populationCountContainer.selectAll('*').remove();
- var subpopulationCount = d3
- .set(
- this.filtered_data.map(function(d) {
- return d[_this.config.id_col];
- })
- )
- .values().length;
- var percentage = d3.format('0.1%')(subpopulationCount / this.populationCount);
- this.populationCountContainer.html(
- '\n' +
- subpopulationCount +
- ' of ' +
- this.populationCount +
- ' participants shown (' +
- percentage +
- ')'
- );
- }
-
- function removeUnscheduledVisits$1() {
- var _this = this;
-
- if (!this.config.unscheduled_visits)
- this.marks.forEach(function(mark) {
- if (mark.type === 'line')
- mark.data.forEach(function(d) {
- d.values = d.values.filter(function(di) {
- return _this.config.x.domain.indexOf(di.key) > -1;
- });
- });
- else if (mark.type === 'circle')
- mark.data = mark.data.filter(function(d) {
- return _this.config.x.domain.indexOf(d.values.x) > -1;
- });
- });
- }
-
- function clearCanvas() {
- this.svg.selectAll('.y.axis .tick').remove();
- this.svg.selectAll('.point').remove(); // mark data doesn't necessarily get updated (?)
- this.svg.selectAll('.boxplot-wrap').remove();
- }
-
- function updateMarkData() {
- var _this = this;
-
- this.marks.forEach(function(mark, i) {
- mark.hidden = _this.config.marks[i].hidden;
- });
- this.marks
- .filter(function(mark) {
- return mark.type === 'circle';
- })
- .forEach(function(mark) {
- mark.data.forEach(function(d, i) {
- d.id = 'outlier-' + i;
- d.hidden = mark.hidden;
- d.visit = d.values.x;
- d.group = d.values.raw[0][_this.config.color_by];
- });
- });
- }
-
- function onDraw() {
- updateParticipantCount.call(this);
- clearCanvas.call(this);
- removeUnscheduledVisits$1.call(this);
- updateMarkData.call(this);
- }
-
- function editXAxisTicks() {
- //Rotate x-axis tick labels.
- if (this.config.time_settings.rotate_tick_labels)
- this.svg
- .selectAll('.x.axis .tick text')
- .attr({
- transform: 'rotate(-45)',
- dx: -10,
- dy: 10
- })
- .style('text-anchor', 'end');
- }
-
- function drawLogAxis() {
- //Draw custom y-axis given a log scale.
- if (this.config.y.type === 'log') {
- var logYAxis = d3.svg
- .axis()
- .scale(this.y)
- .orient('left')
- .ticks(8, ',' + this.config.y.format)
- .tickSize(6, 0);
- this.svg.select('g.y.axis').call(logYAxis);
- }
- }
-
- function handleEmptyAxis() {
- var _this = this;
-
- //Manually draw y-axis ticks when none exist.
- if (this.svg.selectAll('.y .tick').size() < 2) {
- //Define quantiles of current measure results.
- var probs = [
- { probability: 0.1 },
- { probability: 0.3 },
- { probability: 0.5 },
- { probability: 0.7 },
- { probability: 0.9 }
- ];
-
- for (var i = 0; i < probs.length; i++) {
- probs[i].quantile = d3.quantile(
- this.measure_data
- .map(function(d) {
- return +d[_this.config.y.column];
- })
- .sort(function(a, b) {
- return a - b;
- }),
- probs[i].probability
- );
- }
-
- var ticks = probs.map(function(prob) {
- return prob.quantile;
- });
-
- //Manually define y-axis tick values.
- this.yAxis.tickValues(ticks);
-
- //Transition the y-axis to draw the ticks.
- this.svg
- .select('g.y.axis')
- .transition()
- .call(this.yAxis);
-
- //Draw the gridlines.
- this.drawGridlines();
- }
- }
-
- function removeDuplicateTickLabels() {
- //Manually remove excess y-axis ticks.
- var tickLabels = [];
- this.svg.selectAll('.y.axis .tick').each(function(d) {
- var tick = d3.select(this);
- var label = tick.select('text');
-
- if (label.size()) {
- var tickLabel = label.text();
-
- //Check if tick value already exists on axis and if so, remove.
- if (tickLabels.indexOf(tickLabel) < 0) tickLabels.push(tickLabel);
- else label.remove();
- }
- });
- }
-
- function fixFloatingPointIssues() {
- this.svg
- .selectAll('.y.axis .tick text')
- .filter(function(d) {
- return /^\d*\.0*[1-9]0{5,}[1-9]$/.test(d);
- }) // floating point issues, e.g. .2 + .1 !== .3
- .remove();
- }
-
- function editYAxisTicks() {
- drawLogAxis.call(this);
- handleEmptyAxis.call(this);
- removeDuplicateTickLabels.call(this);
- fixFloatingPointIssues.call(this);
- }
-
- function clearCanvas$1() {
- this.svg.selectAll('.boxplot-wrap').remove();
- }
-
- function defineScales(subgroup) {
- subgroup.boxplot.x = d3.scale.linear().range([0, this.x.rangeBand()]);
- subgroup.boxplot.left = subgroup.boxplot.x(0.5 - subgroup.boxplot.boxPlotWidth / 2);
- subgroup.boxplot.right = subgroup.boxplot.x(0.5 + subgroup.boxplot.boxPlotWidth / 2);
- subgroup.boxplot.y =
- this.config.y.type === 'linear'
- ? d3.scale
- .linear()
- .range([this.plot_height, 0])
- .domain(this.y.domain())
- : d3.scale
- .log()
- .range([this.plot_height, 0])
- .domain(this.y.domain());
- }
-
- function addContainer(subgroup) {
- subgroup.boxplot.container = subgroup.svg
- .append('g')
- .attr('class', 'boxplot')
- .datum({
- values: subgroup.results.values,
- probs: subgroup.boxplot.probs
- })
- .attr('clip-path', 'url(#' + this.id + ')');
- }
-
- function drawBox(subgroup) {
- subgroup.boxplot.container
- .append('rect')
- .attr({
- class: 'boxplot fill',
- x: subgroup.boxplot.left,
- width: subgroup.boxplot.right - subgroup.boxplot.left,
- y: subgroup.boxplot.y(subgroup.boxplot.probs[3]),
- height:
- subgroup.boxplot.y(subgroup.boxplot.probs[1]) -
- subgroup.boxplot.y(subgroup.boxplot.probs[3])
- })
- .style('fill', subgroup.boxplot.boxColor);
- }
-
- function drawHorizontalLines(subgroup) {
- var iS = [0, 2, 4];
- var iSclass = ['', 'median', ''];
- var iSColor = [
- subgroup.boxplot.boxColor,
- subgroup.boxplot.boxInsideColor,
- subgroup.boxplot.boxColor
- ];
- for (var i = 0; i < iS.length; i++) {
- subgroup.boxplot.container
- .append('line')
- .attr({
- class: 'boxplot ' + iSclass[i],
- x1: subgroup.boxplot.left,
- x2: subgroup.boxplot.right,
- y1: subgroup.boxplot.y(subgroup.boxplot.probs[iS[i]]),
- y2: subgroup.boxplot.y(subgroup.boxplot.probs[iS[i]])
- })
- .style({
- fill: iSColor[i],
- stroke: iSColor[i]
- });
- }
- }
-
- function drawVerticalLines(subgroup) {
- var iS = [[0, 1], [3, 4]];
- for (var i = 0; i < iS.length; i++) {
- subgroup.boxplot.container
- .append('line')
- .attr({
- class: 'boxplot',
- x1: subgroup.boxplot.x(0.5),
- x2: subgroup.boxplot.x(0.5),
- y1: subgroup.boxplot.y(subgroup.boxplot.probs[iS[i][0]]),
- y2: subgroup.boxplot.y(subgroup.boxplot.probs[iS[i][1]])
- })
- .style('stroke', subgroup.boxplot.boxColor);
- }
- }
-
- function drawOuterCircle(subgroup) {
- subgroup.boxplot.container
- .append('circle')
- .attr({
- class: 'boxplot mean',
- cx: subgroup.boxplot.x(0.5),
- cy: subgroup.boxplot.y(subgroup.results.mean),
- r: Math.min(subgroup.boxplot.x(subgroup.boxplot.boxPlotWidth / 3), 10)
- })
- .style({
- fill: subgroup.boxplot.boxInsideColor,
- stroke: subgroup.boxplot.boxColor
- });
- }
-
- function drawInnerCircle(subgroup) {
- subgroup.boxplot.container
- .append('circle')
- .attr({
- class: 'boxplot mean',
- cx: subgroup.boxplot.x(0.5),
- cy: subgroup.boxplot.y(subgroup.results.mean),
- r: Math.min(subgroup.boxplot.x(subgroup.boxplot.boxPlotWidth / 6), 5)
- })
- .style({
- fill: subgroup.boxplot.boxColor,
- stroke: 'none'
- });
- }
-
- function addBoxPlot(subgroup) {
- //Attach needed stuff to subgroup object.
- subgroup.boxplot = {
- boxPlotWidth: 0.75 / this.colorScale.domain().length,
- boxColor: this.colorScale(subgroup.key),
- boxInsideColor: '#eee',
- probs: ['q5', 'q25', 'median', 'q75', 'q95'].map(function(prob) {
- return subgroup.results[prob];
- })
- };
-
- //Draw box plot.
- defineScales.call(this, subgroup);
- addContainer.call(this, subgroup);
- drawBox.call(this, subgroup);
- drawHorizontalLines.call(this, subgroup);
- drawVerticalLines.call(this, subgroup);
- drawOuterCircle.call(this, subgroup);
- drawInnerCircle.call(this, subgroup);
- }
-
- function defineData(subgroup) {
- //Define histogram data.
- subgroup.violinPlot = {
- histogram: d3.layout
- .histogram()
- .bins(10)
- .frequency(0)
- };
- (subgroup.violinPlot.data = subgroup.violinPlot.histogram(subgroup.results.values)),
- subgroup.violinPlot.data.unshift({
- x: subgroup.results.min,
- dx: 0,
- y: subgroup.violinPlot.data[0].y
- });
- subgroup.violinPlot.data.push({
- x: subgroup.results.max,
- dx: 0,
- y: subgroup.violinPlot.data[subgroup.violinPlot.data.length - 1].y
- });
- }
-
- function defineScales$1(subgroup) {
- subgroup.violinPlot.width = this.x.rangeBand();
- subgroup.violinPlot.x =
- this.config.y.type === 'linear'
- ? d3.scale
- .linear()
- .domain(this.y.domain())
- .range([this.plot_height, 0])
- : d3.scale
- .log()
- .domain(this.y.domain())
- .range([this.plot_height, 0]);
- subgroup.violinPlot.y = d3.scale
- .linear()
- .domain([
- 0,
- Math.max(
- 1 - 1 / subgroup.group.x.nGroups,
- d3.max(subgroup.violinPlot.data, function(d) {
- return d.y;
- })
- )
- ])
- .range([subgroup.violinPlot.width / 2, 0]);
- }
-
- function addContainer$1(subgroup) {
- //Define violin shapes.
- subgroup.violinPlot.area = d3.svg
- .area()
- .interpolate('basis')
- .x(function(d) {
- return subgroup.violinPlot.x(d.x + d.dx / 2);
- })
- .y0(subgroup.violinPlot.width / 2)
- .y1(function(d) {
- return subgroup.violinPlot.y(d.y);
- });
- subgroup.violinPlot.line = d3.svg
- .line()
- .interpolate('basis')
- .x(function(d) {
- return subgroup.violinPlot.x(d.x + d.dx / 2);
- })
- .y(function(d) {
- return subgroup.violinPlot.y(d.y);
- });
- subgroup.violinPlot.container = subgroup.svg
- .append('g')
- .attr('class', 'violinplot')
- .attr('clip-path', 'url(#' + this.id + ')');
- }
-
- function drawLeftSide(subgroup) {
- subgroup.violinPlot.gMinus = subgroup.violinPlot.container
- .append('g')
- .attr('transform', 'rotate(90,0,0) scale(1,-1)');
- subgroup.violinPlot.gMinus
- .append('path')
- .datum(subgroup.violinPlot.data)
- .attr({
- class: 'area',
- d: subgroup.violinPlot.area,
- fill: this.colorScale(subgroup.key),
- 'fill-opacity': 0.75
- });
- subgroup.violinPlot.gMinus
- .append('path')
- .datum(subgroup.violinPlot.data)
- .attr({
- class: 'violin',
- d: subgroup.violinPlot.line,
- stroke: this.colorScale(subgroup.key),
- fill: 'none'
- });
- }
-
- function drawRightSide(subgroup) {
- subgroup.violinPlot.gPlus = subgroup.violinPlot.container
- .append('g')
- .attr('transform', 'rotate(90,0,0) translate(0,-' + subgroup.violinPlot.width + ')');
- subgroup.violinPlot.gPlus
- .append('path')
- .datum(subgroup.violinPlot.data)
- .attr({
- class: 'area',
- d: subgroup.violinPlot.area,
- fill: this.colorScale(subgroup.key),
- 'fill-opacity': 0.75
- });
- subgroup.violinPlot.gPlus
- .append('path')
- .datum(subgroup.violinPlot.data)
- .attr({
- class: 'violin',
- d: subgroup.violinPlot.line,
- stroke: this.colorScale(subgroup.key),
- fill: 'none'
- });
- }
-
- function addViolinPlot(subgroup) {
- defineData.call(this, subgroup);
- defineScales$1.call(this, subgroup);
- addContainer$1.call(this, subgroup);
- drawLeftSide.call(this, subgroup);
- drawRightSide.call(this, subgroup);
- }
-
- function addSummaryStatistics(subgroup) {
- var format0 = d3.format('.' + (this.config.y.precision + 0) + 'f');
- var format1 = d3.format('.' + (this.config.y.precision + 1) + 'f');
- var format2 = d3.format('.' + (this.config.y.precision + 2) + 'f');
- subgroup.svg
- .selectAll('g')
- .append('title')
- .html(function(d) {
- return (
- subgroup.key +
- ' at ' +
- subgroup.group.x.key +
- ':\n N = ' +
- subgroup.results.n +
- '\n Min = ' +
- format0(subgroup.results.min) +
- '\n 5th % = ' +
- format1(subgroup.results.q5) +
- '\n Q1 = ' +
- format1(subgroup.results.q25) +
- '\n Median = ' +
- format1(subgroup.results.median) +
- '\n Q3 = ' +
- format1(subgroup.results.q75) +
- '\n 95th % = ' +
- format1(subgroup.results.q95) +
- '\n Max = ' +
- format0(subgroup.results.max) +
- '\n Mean = ' +
- format1(subgroup.results.mean) +
- '\n StDev = ' +
- format2(subgroup.results.deviation)
- );
- });
- }
-
- function drawPlots() {
- var _this = this;
-
- this.nested_measure_data
- .filter(function(visit) {
- return _this.x_dom.indexOf(visit.key) > -1;
- })
- .forEach(function(visit) {
- // iterate over groups
- //Sort [ config.color_by ] groups.
- visit.values = visit.values.sort(function(a, b) {
- return _this.colorScale.domain().indexOf(a.key) <
- _this.colorScale.domain().indexOf(b.key)
- ? -1
- : 1;
- });
-
- //Define group object.
- var groupObject = {
- x: {
- key: visit.key, // x-axis value
- nGroups: _this.colorScale.domain().length, // number of groups at x-axis value
- width: _this.x.rangeBand() // width of x-axis value
- },
- subgroups: []
- };
- groupObject.x.start = -(groupObject.x.nGroups / 2) + 0.5;
- groupObject.distance = groupObject.x.width / groupObject.x.nGroups;
-
- visit.values.forEach(function(group, i) {
- //Iterate over visits.
- var subgroup = {
- group: groupObject,
- key: group.key,
- offset: (groupObject.x.start + i) * groupObject.distance,
- results: group.values
- };
- subgroup.svg = _this.svg
- .insert('g', '.point-supergroup')
- .attr({
- class: 'boxplot-wrap overlay-item',
- transform:
- 'translate(' +
- (_this.x(groupObject.x.key) + subgroup.offset) +
- ',0)'
- })
- .datum({ values: subgroup.results });
- groupObject.subgroups.push(subgroup);
-
- if (_this.config.boxplots) addBoxPlot.call(_this, subgroup);
- if (_this.config.violins) addViolinPlot.call(_this, subgroup);
- addSummaryStatistics.call(_this, subgroup);
-
- //Offset outliers.
- _this.marks
- .filter(function(mark) {
- return mark.type === 'circle';
- })
- .forEach(function(mark) {
- mark.groups
- .filter(function(d) {
- return d.visit === visit.key && d.group === group.key;
- })
- .attr('transform', 'translate(' + subgroup.offset + ',0)');
- });
- });
- });
- }
-
- function addMouseoverToOutliers() {
- var _this = this;
-
- this.marks
- .filter(function(mark) {
- return mark.type === 'circle';
- })
- .forEach(function(mark) {
- mark.groups
- .each(function(d, i) {
- d3.select(this).classed('hidden-' + d.hidden + ' ' + d.id, true);
- })
- .on('mouseover', function(d) {
- _this.svg.select('.hidden-true.' + d.id + ' circle').attr({
- 'fill-opacity': 1,
- 'stroke-opacity': 1
- });
- })
- .on('mouseout', function(d) {
- _this.svg.select('.hidden-true.' + d.id + ' circle').attr({
- 'fill-opacity': 0,
- 'stroke-opacity': 0
- });
- });
- });
- }
-
- function removeLegend() {
- if (this.config.color_by === 'srot_none') this.wrap.select('.legend').remove();
- }
-
- function onResize() {
- editXAxisTicks.call(this);
- editYAxisTicks.call(this);
- clearCanvas$1.call(this);
- drawPlots.call(this);
- addMouseoverToOutliers.call(this);
- removeLegend.call(this);
- }
-
- function onDestroy() {}
-
- var callbacks = {
- onInit: onInit,
- onLayout: onLayout,
- onPreprocess: onPreprocess,
- onDatatransform: onDatatransform,
- onDraw: onDraw,
- onResize: onResize,
- onDestroy: onDestroy
- };
-
- function safetyResultsOverTime(element, settings) {
- var mergedSettings = merge(configuration.defaultSettings, settings); //Merge user settings onto default settings.
- var syncedSettings = configuration.syncSettings(mergedSettings); //Sync properties within merged settings, e.g. data mappings.
- var syncedControlInputs = configuration.syncControlInputs(
- configuration.controlInputs(),
- syncedSettings
- ); //Sync merged settings with controls.
-
- //Define controls.
- var controls = webcharts.createControls(element, {
- location: 'top',
- inputs: syncedControlInputs
- });
-
- //Define chart.
- var chart = webcharts.createChart(element, syncedSettings, controls);
-
- //Attach callbacks to chart.
- for (var callback in callbacks) {
- chart.on(callback.substring(2).toLowerCase(), callbacks[callback]);
- }
- return chart;
- }
-
- return safetyResultsOverTime;
-});
diff --git a/inst/htmlwidgets/lib/safety-shift-plot-2.1.3/safetyShiftPlot.js b/inst/htmlwidgets/lib/safety-shift-plot-2.1.3/safetyShiftPlot.js
deleted file mode 100644
index 637660ac..00000000
--- a/inst/htmlwidgets/lib/safety-shift-plot-2.1.3/safetyShiftPlot.js
+++ /dev/null
@@ -1,1399 +0,0 @@
-(function(global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined'
- ? (module.exports = factory(require('d3'), require('webcharts')))
- : typeof define === 'function' && define.amd
- ? define(['d3', 'webcharts'], factory)
- : (global.safetyShiftPlot = factory(global.d3, global.webCharts));
-})(this, function(d3, webcharts) {
- 'use strict';
-
- if (typeof Object.assign != 'function') {
- // Must be writable: true, enumerable: false, configurable: true
- Object.defineProperty(Object, 'assign', {
- value: function assign(target, varArgs) {
- if (target == null) {
- // TypeError if undefined or null
- throw new TypeError('Cannot convert undefined or null to object');
- }
-
- var to = Object(target);
-
- for (var index = 1; index < arguments.length; index++) {
- var nextSource = arguments[index];
-
- if (nextSource != null) {
- // Skip over if undefined or null
- for (var nextKey in nextSource) {
- // Avoid bugs when hasOwnProperty is shadowed
- if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
- to[nextKey] = nextSource[nextKey];
- }
- }
- }
- }
-
- return to;
- },
- writable: true,
- configurable: true
- });
- }
-
- if (!Array.prototype.find) {
- Object.defineProperty(Array.prototype, 'find', {
- value: function value(predicate) {
- // 1. Let O be ? ToObject(this value).
- if (this == null) {
- throw new TypeError('"this" is null or not defined');
- }
-
- var o = Object(this);
-
- // 2. Let len be ? ToLength(? Get(O, 'length')).
- var len = o.length >>> 0;
-
- // 3. If IsCallable(predicate) is false, throw a TypeError exception.
- if (typeof predicate !== 'function') {
- throw new TypeError('predicate must be a function');
- }
-
- // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
- var thisArg = arguments[1];
-
- // 5. Let k be 0.
- var k = 0;
-
- // 6. Repeat, while k < len
- while (k < len) {
- // a. Let Pk be ! ToString(k).
- // b. Let kValue be ? Get(O, Pk).
- // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)).
- // d. If testResult is true, return kValue.
- var kValue = o[k];
- if (predicate.call(thisArg, kValue, k, o)) {
- return kValue;
- }
- // e. Increase k by 1.
- k++;
- }
-
- // 7. Return undefined.
- return undefined;
- }
- });
- }
-
- if (!Array.prototype.findIndex) {
- Object.defineProperty(Array.prototype, 'findIndex', {
- value: function value(predicate) {
- // 1. Let O be ? ToObject(this value).
- if (this == null) {
- throw new TypeError('"this" is null or not defined');
- }
-
- var o = Object(this);
-
- // 2. Let len be ? ToLength(? Get(O, "length")).
- var len = o.length >>> 0;
-
- // 3. If IsCallable(predicate) is false, throw a TypeError exception.
- if (typeof predicate !== 'function') {
- throw new TypeError('predicate must be a function');
- }
-
- // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
- var thisArg = arguments[1];
-
- // 5. Let k be 0.
- var k = 0;
-
- // 6. Repeat, while k < len
- while (k < len) {
- // a. Let Pk be ! ToString(k).
- // b. Let kValue be ? Get(O, Pk).
- // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)).
- // d. If testResult is true, return k.
- var kValue = o[k];
- if (predicate.call(thisArg, kValue, k, o)) {
- return k;
- }
- // e. Increase k by 1.
- k++;
- }
-
- // 7. Return -1.
- return -1;
- }
- });
- }
-
- var rendererSpecificSettings = {
- id_col: 'USUBJID',
- time_col: 'VISITN',
- visit_col: 'VISIT',
- visit_order_col: 'VISITNUM',
- measure_col: 'TEST',
- value_col: 'STRESN',
- start_value: null,
- x_params: { visits: null, stat: 'mean' },
- y_params: { visits: null, stat: 'mean' },
- filters: null
- };
-
- var webchartsSettings = {
- x: {
- column: 'shiftx',
- type: 'linear',
- label: 'Baseline Value',
- format: '0.2f'
- },
- y: {
- column: 'shifty',
- type: 'linear',
- label: 'Comparison Value',
- behavior: 'flex',
- format: '0.2f'
- },
- marks: [
- {
- type: 'circle',
- per: ['key'],
- radius: 4,
- attributes: {
- 'stroke-width': 0.5,
- 'fill-opacity': 0.8
- },
- tooltip:
- 'Subject ID: [key]\nBaseline: [shiftx]\nComparison: [shifty]\nChange: [chg]\nPercent Change: [pchg]'
- }
- ],
- gridlines: 'xy',
- resizable: false,
- margin: { right: 25, top: 25 },
- aspect: 1
- };
-
- var defaultSettings = Object.assign({}, rendererSpecificSettings, webchartsSettings);
-
- // Replicate settings in multiple places in the settings object
- function syncSettings(settings) {
- if (!(settings.filters instanceof Array))
- settings.filters = typeof settings.filters === 'string' ? [settings.filters] : [];
-
- settings.measure = settings.start_value;
- return settings;
- }
-
- // Default Control objects
- var controlInputs = [
- { type: 'dropdown', values: [], label: 'Measure', option: 'measure', require: true },
- {
- type: 'dropdown',
- values: [],
- label: 'Baseline visit(s)',
- option: 'x_params_visits',
- require: true,
- multiple: true
- },
- {
- type: 'dropdown',
- values: [],
- label: 'Comparison visit(s)',
- option: 'y_params_visits',
- require: true,
- multiple: true
- }
- ];
-
- // Map values from settings to control inputs
- function syncControlInputs(controlInputs, settings) {
- //Define filter objects.
- if (Array.isArray(settings.filters) && settings.filters.length)
- settings.filters = settings.filters.map(function(filter) {
- var filterObject = {
- value_col: filter.value_col || filter
- };
- filterObject.label = filter.label || filterObject.value_col;
- filterObject.type = 'subsetter';
-
- if (filter instanceof Object) Object.assign(filterObject, filter);
-
- return filterObject;
- });
- else delete settings.filters;
-
- return controlInputs;
- }
-
- var listingSettings = {
- cols: ['key', 'shiftx', 'shifty', 'chg', 'pchg'],
- headers: ['Participant ID', 'Baseline', 'Comparison', 'Change', 'Percent Change'],
- searchable: false,
- sortable: true,
- pagination: false,
- exportable: true
- };
-
- var _typeof =
- typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol'
- ? function(obj) {
- return typeof obj;
- }
- : function(obj) {
- return obj &&
- typeof Symbol === 'function' &&
- obj.constructor === Symbol &&
- obj !== Symbol.prototype
- ? 'symbol'
- : typeof obj;
- };
-
- function clone(obj) {
- var copy;
-
- //boolean, number, string, null, undefined
- if ('object' != (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) || null == obj)
- return obj;
-
- //date
- if (obj instanceof Date) {
- copy = new Date();
- copy.setTime(obj.getTime());
- return copy;
- }
-
- //array
- if (obj instanceof Array) {
- copy = [];
- for (var i = 0, len = obj.length; i < len; i++) {
- copy[i] = clone(obj[i]);
- }
- return copy;
- }
-
- //object
- if (obj instanceof Object) {
- copy = {};
- for (var attr in obj) {
- if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
- }
- return copy;
- }
-
- throw new Error('Unable to copy [obj]! Its type is not supported.');
- }
-
- var isMergeableObject = function isMergeableObject(value) {
- return isNonNullObject(value) && !isSpecial(value);
- };
-
- function isNonNullObject(value) {
- return (
- !!value && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object'
- );
- }
-
- function isSpecial(value) {
- var stringValue = Object.prototype.toString.call(value);
-
- return (
- stringValue === '[object RegExp]' ||
- stringValue === '[object Date]' ||
- isReactElement(value)
- );
- }
-
- // see https://github.com/facebook/react/blob/b5ac963fb791d1298e7f396236383bc955f916c1/src/isomorphic/classic/element/ReactElement.js#L21-L25
- var canUseSymbol = typeof Symbol === 'function' && Symbol.for;
- var REACT_ELEMENT_TYPE = canUseSymbol ? Symbol.for('react.element') : 0xeac7;
-
- function isReactElement(value) {
- return value.$$typeof === REACT_ELEMENT_TYPE;
- }
-
- function emptyTarget(val) {
- return Array.isArray(val) ? [] : {};
- }
-
- function cloneUnlessOtherwiseSpecified(value, options) {
- return options.clone !== false && options.isMergeableObject(value)
- ? deepmerge(emptyTarget(value), value, options)
- : value;
- }
-
- function defaultArrayMerge(target, source, options) {
- return target.concat(source).map(function(element) {
- return cloneUnlessOtherwiseSpecified(element, options);
- });
- }
-
- function mergeObject(target, source, options) {
- var destination = {};
-
- if (options.isMergeableObject(target)) {
- Object.keys(target).forEach(function(key) {
- destination[key] = cloneUnlessOtherwiseSpecified(target[key], options);
- });
- }
-
- Object.keys(source).forEach(function(key) {
- if (!options.isMergeableObject(source[key]) || !target[key]) {
- destination[key] = cloneUnlessOtherwiseSpecified(source[key], options);
- } else {
- destination[key] = deepmerge(target[key], source[key], options);
- }
- });
-
- return destination;
- }
-
- function deepmerge(target, source, options) {
- options = options || {};
-
- options.arrayMerge = options.arrayMerge || defaultArrayMerge;
-
- options.isMergeableObject = options.isMergeableObject || isMergeableObject;
-
- var sourceIsArray = Array.isArray(source);
-
- var targetIsArray = Array.isArray(target);
-
- var sourceAndTargetTypesMatch = sourceIsArray === targetIsArray;
-
- if (!sourceAndTargetTypesMatch) {
- return cloneUnlessOtherwiseSpecified(source, options);
- } else if (sourceIsArray) {
- return options.arrayMerge(target, source, options);
- } else {
- return mergeObject(target, source, options);
- }
- }
-
- deepmerge.all = function deepmergeAll(array, options) {
- if (!Array.isArray(array)) {
- throw new Error('first argument should be an array');
- }
-
- return array.reduce(function(prev, next) {
- return deepmerge(prev, next, options);
- }, {});
- };
-
- var deepmerge_1 = deepmerge;
-
- function defineLayout(element) {
- var container = d3.select(element);
- container
- .append('div')
- .classed('ssp-component', true)
- .attr('id', 'ssp-controls');
- container
- .append('div')
- .classed('ssp-component', true)
- .attr('id', 'ssp-chart');
- container
- .append('div')
- .classed('ssp-component', true)
- .attr('id', 'ssp-listing');
- }
-
- function defineStyles() {
- var styles = [
- '#safety-shift-plot {' + ' width: 100%;' + ' display: inline-block;' + '}',
- '.ssp-component {' +
- ' margin: 0;' +
- ' border: none;' +
- ' padding: 0;' +
- ' display: inline-block;' +
- '}',
-
- //controls
- '#ssp-controls {' + ' width: 25%;' + ' float: left;' + '}',
- '#ssp-controls .control-group {' +
- ' width: 98%;' +
- ' margin: 0 2% 5px 0;' +
- ' padding: 0;' +
- '}',
- '#ssp-controls .control-group > * {' + ' display: inline-block;' + '}',
- '#ssp-controls .changer {' + ' float: right;' + ' width: 50%;' + '}',
- '#ssp-controls .wc-control-label {' +
- ' text-align: right;' +
- ' width: 48%;' +
- '}',
- '#ssp-controls .annote {' + ' width: 98%;' + ' text-align: right;' + '}',
-
- //chart
- '#ssp-chart {' + ' width: 36%;' + ' margin: 0 2%;' + '}',
-
- //listing
- '#ssp-listing {' + ' width: 35%;' + ' float: right;' + '}',
- '#ssp-listing .wc-table table {' + ' width: 100%;' + ' display: table;' + '}',
- '#ssp-listing .wc-table th:not(:first-child),' +
- '#ssp-listing .wc-table td:not(:first-child) {' +
- ' text-align: right;' +
- '}'
- ];
- var style = document.createElement('style');
- style.type = 'text/css';
- style.innerHTML = styles.join('\n');
- document.getElementsByTagName('head')[0].appendChild(style);
- }
-
- function cleanData() {
- var _this = this;
-
- //Remove missing and non-numeric data.
- var preclean = this.raw_data;
- var clean = this.raw_data.filter(function(d) {
- return /^-?[0-9.]+$/.test(d[_this.config.value_col]);
- });
- var nPreclean = preclean.length;
- var nClean = clean.length;
- var nRemoved = nPreclean - nClean;
-
- //Warn user of removed records.
- if (nRemoved > 0)
- console.warn(
- nRemoved +
- ' missing or non-numeric result' +
- (nRemoved > 1 ? 's have' : ' has') +
- ' been removed.'
- );
-
- //Preserve cleaned data.
- this.initial_data = clean;
- }
-
- function addVariables() {
- var _this = this;
-
- this.initial_data.forEach(function(d) {
- d[_this.config.measure_col] = d[_this.config.measure_col].trim();
- });
- }
-
- function checkFilters() {
- var _this = this;
-
- if (this.config.filters)
- this.config.filters = this.config.filters.filter(function(filter) {
- var variableExists = _this.raw_data[0].hasOwnProperty(filter.value_col);
- var nLevels = d3
- .set(
- _this.raw_data.map(function(d) {
- return d[filter.value_col];
- })
- )
- .values().length;
-
- if (!variableExists)
- console.warn(
- ' The [ ' +
- filter.label +
- ' ] filter has been removed because the variable does not exist.'
- );
- else if (nLevels < 2)
- console.warn(
- 'The [ ' +
- filter.label +
- ' ] filter has been removed because the variable has only one level.'
- );
-
- return variableExists && nLevels > 1;
- });
- }
-
- function getMeasures() {
- var _this = this;
-
- this.measures = d3
- .set(
- this.initial_data.map(function(d) {
- return d[_this.config.measure_col];
- })
- )
- .values()
- .sort();
- }
-
- function getVisits() {
- var _this = this;
-
- if (
- this.config.visit_order_col &&
- this.initial_data[0].hasOwnProperty(this.config.visit_order_col)
- )
- this.visits = d3
- .set(
- this.initial_data.map(function(d) {
- return d[_this.config.visit_col] + '||' + d[_this.config.visit_order_col];
- })
- )
- .values()
- .sort(function(a, b) {
- var aSplit = a.split('||');
- var aVisit = aSplit[0];
- var aOrder = aSplit[1];
- var bSplit = b.split('||');
- var bVisit = bSplit[0];
- var bOrder = bSplit[1];
- var diff = aOrder - bOrder;
- return diff
- ? diff
- : aOrder < bOrder
- ? -1
- : aOrder > bOrder
- ? 1
- : aVisit < bVisit
- ? -1
- : 1;
- })
- .map(function(visit) {
- return visit.split('||')[0];
- });
- else
- this.visits = d3
- .set(
- this.initial_data.map(function(d) {
- return d[_this.config.visit_col];
- })
- )
- .values()
- .sort();
- }
-
- function updateControlInputs() {
- this.controls.config.inputs.find(function(input) {
- return input.option === 'measure';
- }).values = this.measures;
- this.controls.config.inputs.find(function(input) {
- return input.option === 'x_params_visits';
- }).values = this.visits;
- this.controls.config.inputs.find(function(input) {
- return input.option === 'y_params_visits';
- }).values = this.visits;
- }
-
- function preprocessData(rawData) {
- var config = this.config;
-
- var nested = d3
- .nest()
- .key(function(d) {
- return d[config.id_col];
- })
- .key(function(d) {
- return d[config.visit_col];
- })
- .key(function(d) {
- return d[config.measure_col];
- })
- .rollup(function(r) {
- var value = r[0][config.value_col];
- return { value: value, raw: r[0] };
- })
- .entries(rawData);
-
- function getMean(arr) {
- return d3.sum(arr) / arr.length;
- }
-
- function setVal(e, params) {
- var visits = e.values.filter(function(f) {
- return params.visits.indexOf(f.key) !== -1;
- });
- var measures = visits.length
- ? d3.merge(
- visits.map(function(m) {
- return m.values
- .filter(function(f) {
- return f.key === config.measure;
- })
- .map(function(p) {
- return +p.values.value;
- });
- })
- )
- : [];
-
- var meas = null;
- var stat = measures && measures.length > 1 ? params.stat : 'def';
- var something = {
- mean: getMean(measures),
- max: d3.max(measures),
- min: d3.min(measures),
- def: measures[0]
- };
- meas = something[stat];
- return meas;
- }
-
- function getXY(e) {
- e.shiftx = +setVal(e, config.x_params);
- e.shifty = +setVal(e, config.y_params);
- e.chg = e.shifty - e.shiftx;
- e.pchg = d3.format('%')(e.chg / e.shiftx);
- }
-
- function getChange(e) {
- e.shifty -= +e.shiftx;
- }
-
- //flatten out other columns specified for details
- function getOther(e) {
- config.details.forEach(function(g) {
- e[g.col] = e.values[0].values[0].values.raw[g.col];
- });
- }
-
- config.details = config.details && config.details.length ? config.details : [];
-
- if (config.color_by) {
- var match = config.details.filter(function(f) {
- return f.col === config.color_by;
- });
- if (!match[0]) config.details.push({ col: config.color_by, label: config.color_by });
- }
-
- var test_data = nested;
- test_data.forEach(getXY);
- if (config.change) test_data.forEach(getChange);
- if (config.details.length) test_data.forEach(getOther);
-
- return test_data;
- }
-
- function initCustomEvents() {
- var chart = this;
- chart.participantsSelected = [];
- chart.events.participantsSelected = new CustomEvent('participantsSelected');
- }
-
- function onInit() {
- var _this = this;
-
- // 1. Remove invalid data.
- cleanData.call(this);
-
- // 2. Add/edit variables.
- addVariables.call(this);
-
- // 3a Check filters against data.
- checkFilters.call(this);
-
- // 3b Get list of measures.
- getMeasures.call(this);
-
- // 3c Get list of visits.
- getVisits.call(this);
-
- // 4. Update control inputs.
- updateControlInputs.call(this);
-
- //Set initial measure.
- this.config.measure = this.config.measure || this.measures[0];
-
- //Set baseline and comparison visits.
- this.config.x_params.visits = this.config.x_params.visits || [this.visits[0]];
- this.config.y_params.visits = this.config.y_params.visits || this.visits.slice(1);
-
- //Filter raw data on initial measure and derive baseline/comparison data.
- this.measureData = this.initial_data.filter(function(d) {
- return d[_this.config.measure_col] === _this.config.measure;
- });
- this.filteredData = this.measureData; // filtered data placeholder
- this.raw_data = preprocessData.call(this, this.measureData); // preprocessed measure data
-
- //Define initial domains.
- this.config.x.domain = d3.extent(
- this.raw_data.map(function(d) {
- return d.shiftx;
- })
- );
- this.config.y.domain = d3.extent(
- this.raw_data.map(function(d) {
- return d.shifty;
- })
- );
-
- //initialize custom events
- initCustomEvents.call(this);
- }
-
- function custmoizeMeasureControl() {
- var _this = this;
-
- var measureSelect = this.controls.wrap
- .selectAll('.control-group')
- .filter(function(f) {
- return f.option === 'measure';
- })
- .select('select');
- measureSelect.on('change', function() {
- _this.config.measure = measureSelect.select('option:checked').property('text');
-
- //Redefine raw and preprocessed measure data, x-domain, and y-domain.
- _this.measureData = _this.initial_data.filter(function(d) {
- return d[_this.config.measure_col] === _this.config.measure;
- });
- _this.raw_data = preprocessData.call(_this, _this.measureData);
- _this.config.x.domain = d3.extent(
- _this.raw_data.map(function(d) {
- return d.shiftx;
- })
- );
- _this.config.y.domain = d3.extent(
- _this.raw_data.map(function(d) {
- return d.shifty;
- })
- );
-
- //Redefine and preprocess filtered data and redraw chart.
- if (_this.config.filters) {
- _this.filteredData = _this.measureData.filter(function(d) {
- var filtered = false;
- _this.config.filters.forEach(function(filter) {
- return (filtered =
- filtered === false && filter.value !== 'All'
- ? d[filter.value_col] !== filter.value
- : filtered);
- });
- return !filtered;
- });
- var filteredPreprocessedData = preprocessData.call(_this, _this.filteredData);
- _this.draw(filteredPreprocessedData);
- } else {
- _this.filteredData = _this.measureData;
- _this.draw(_this.raw_data);
- }
- });
- }
-
- function customizeBaselineControl() {
- var _this = this;
-
- var baselineSelect = this.controls.wrap
- .selectAll('.control-group')
- .filter(function(f) {
- return f.option === 'x_params_visits';
- })
- .select('select');
- baselineSelect
- .selectAll('option')
- .filter(function(f) {
- return _this.config.x_params.visits.indexOf(f) > -1;
- })
- .attr('selected', 'selected');
- baselineSelect.on('change', function() {
- _this.config.x_params.visits = baselineSelect.selectAll('option:checked').data();
-
- //Redefine preprocessed measure data and x-domain.
- _this.raw_data = preprocessData.call(_this, _this.measureData);
- _this.config.x.domain = d3.extent(
- _this.raw_data.map(function(d) {
- return d.shiftx;
- })
- );
-
- //Preprocess filtered data and redraw chart.
- if (_this.config.filters) {
- var filteredPreprocessedData = preprocessData.call(_this, _this.filteredData);
- _this.draw(filteredPreprocessedData);
- } else _this.draw(_this.raw_data);
- });
- }
-
- function customizeComparisonControl() {
- var _this = this;
-
- var comparisonSelect = this.controls.wrap
- .selectAll('.control-group')
- .filter(function(f) {
- return f.option === 'y_params_visits';
- })
- .select('select');
- comparisonSelect
- .selectAll('option')
- .filter(function(f) {
- return _this.config.y_params.visits.indexOf(f) > -1;
- })
- .attr('selected', 'selected');
- comparisonSelect.on('change', function() {
- _this.config.y_params.visits = comparisonSelect.selectAll('option:checked').data();
-
- //Redefine preprocessed measure data and y-domain.
- _this.raw_data = preprocessData.call(_this, _this.measureData);
- _this.config.y.domain = d3.extent(
- _this.raw_data.map(function(d) {
- return d.shifty;
- })
- );
-
- //Preprocess filtered data and redraw chart.
- if (_this.config.filters) {
- var filteredPreprocessedData = preprocessData.call(_this, _this.filteredData);
- _this.draw(filteredPreprocessedData);
- } else _this.draw(_this.raw_data);
- });
- }
-
- function addFilters(chart) {
- chart.config.filters.forEach(function(filter) {
- //Capture distinct [filter.value_col] values.
- filter.values = d3
- .set(
- chart.initial_data.map(function(d) {
- return d[filter.value_col];
- })
- )
- .values();
- filter.value = 'All';
-
- //Attach filter to the DOM.
- var controlGroup = chart.controls.wrap
- .append('div')
- .classed('control-group', true)
- .datum(filter);
- controlGroup
- .append('span')
- .classed('wc-control-label', true)
- .text(filter.label);
- var changer = controlGroup.append('select').classed('changer', true);
-
- //Attach distinct [filter.value_col] values as select options.
- changer
- .selectAll('option')
- .data(['All'].concat(filter.values))
- .enter()
- .append('option')
- .text(function(d) {
- return d;
- });
-
- //Define dropdown event listener.
- changer.on('change', function(d) {
- //Set [filter.value] to dropdown selection.
- filter.value = changer.select('option:checked').property('text');
-
- //Filter raw measure data on all filter selections.
- chart.filteredData = chart.measureData.filter(function(di) {
- var filtered = false;
- chart.config.filters.forEach(function(dii) {
- return (filtered =
- filtered === false && dii.value !== 'All'
- ? di[dii.value_col] !== dii.value
- : filtered);
- });
- return !filtered;
- });
-
- //Preprocess filtered data and redraw chart.
- var preprocessedFilteredData = preprocessData.call(chart, chart.filteredData);
- chart.draw(preprocessedFilteredData);
- });
- });
- }
-
- function onLayout() {
- //Add footnote element.
- this.wrap
- .insert('p', ':first-child')
- .attr('class', 'record-note')
- .style('text-align', 'center')
- .style('font-weight', 'bold')
- .text('Click and drag to select points.');
-
- //Add header element in which to list visits at which measure is captured.
- this.wrap.append('p', 'svg').attr('class', 'possible-visits');
-
- //Designate chart container for brushing.
- this.wrap.classed('brushable', true);
-
- //Customize measure, baseline, and comparison controls.
- custmoizeMeasureControl.call(this);
- customizeBaselineControl.call(this);
- customizeComparisonControl.call(this);
-
- //Create custom filters.
- if (this.config.filters) addFilters(this);
-
- //Add element for participant counts.
- this.controls.wrap
- .append('em')
- .classed('annote', true)
- .style('display', 'block');
- }
-
- function onPreprocess() {}
-
- function onDataTransform() {}
-
- /*------------------------------------------------------------------------------------------------\
- Annotate number of participants based on current filters, number of participants in all, and
- the corresponding percentage.
-
- Inputs:
-
- chart - a webcharts chart object
- id_unit - a text string to label the units in the annotation (default = 'participants')
- selector - css selector for the annotation
- \------------------------------------------------------------------------------------------------*/
-
- function updateParticipantCount(chart, selector, id_unit) {
- //count the number of unique ids in the data set
- var totalObs = d3
- .set(
- chart.initial_data.map(function(d) {
- return d[chart.config.id_col];
- })
- )
- .values().length;
-
- //count the number of unique ids in the current chart and calculate the percentage
- var currentObs = chart.filtered_data.filter(function(d) {
- return (
- chart.x.domain()[0] <= d.shiftx &&
- d.shiftx <= chart.x.domain()[1] &&
- chart.y.domain()[0] <= d.shifty &&
- d.shifty <= chart.y.domain()[1]
- );
- }).length;
-
- var percentage = d3.format('0.1%')(currentObs / totalObs);
-
- //clear the annotation
- var annotation = d3.select(selector);
- annotation.selectAll('*').remove();
-
- //update the annotation
- var units = id_unit ? ' ' + id_unit : ' participant(s)';
- annotation.text(currentObs + ' of ' + totalObs + units + ' shown (' + percentage + ')');
- }
-
- function reset() {
- this.svg.selectAll('g.boxplot').remove();
- this.svg
- .selectAll('g.point')
- .classed('selected', false)
- .select('circle')
- .style('fill', this.config.colors[0]);
- this.wrap
- .select('.record-note')
- .style('text-align', 'center')
- .text('Click and drag to select points.');
- this.svg.select('line.identity').remove();
- this.listing.draw([]);
- this.listing.wrap.style('display', 'none');
- }
-
- function onDraw() {
- //Annotate selected and total number of participants.
- updateParticipantCount(this, '.annote');
-
- //Reset things.
- reset.call(this);
- }
-
- function drawBoxPlot(
- svg,
- results,
- height,
- width,
- domain,
- boxPlotWidth,
- boxColor,
- boxInsideColor,
- fmt,
- horizontal
- ) {
- //set default orientation to "horizontal"
- var horizontal = horizontal == undefined ? true : horizontal;
-
- //make the results numeric and sort
- var results = results
- .map(function(d) {
- return +d;
- })
- .sort(d3.ascending);
-
- //set up scales
- var y = d3.scale.linear().range([height, 0]);
-
- var x = d3.scale.linear().range([0, width]);
-
- if (horizontal) {
- y.domain(domain);
- } else {
- x.domain(domain);
- }
-
- var probs = [0.05, 0.25, 0.5, 0.75, 0.95];
- for (var i = 0; i < probs.length; i++) {
- probs[i] = d3.quantile(results, probs[i]);
- }
-
- var boxplot = svg
- .append('g')
- .attr('class', 'boxplot')
- .datum({ values: results, probs: probs });
-
- //draw rectangle from q1 to q3
- var box_x = horizontal ? x(0.5 - boxPlotWidth / 2) : x(probs[1]);
- var box_width = horizontal
- ? x(0.5 + boxPlotWidth / 2) - x(0.5 - boxPlotWidth / 2)
- : x(probs[3]) - x(probs[1]);
- var box_y = horizontal ? y(probs[3]) : y(0.5 + boxPlotWidth / 2);
- var box_height = horizontal
- ? -y(probs[3]) + y(probs[1])
- : y(0.5 - boxPlotWidth / 2) - y(0.5 + boxPlotWidth / 2);
-
- boxplot
- .append('rect')
- .attr('class', 'boxplot fill')
- .attr('x', box_x)
- .attr('width', box_width)
- .attr('y', box_y)
- .attr('height', box_height)
- .style('fill', boxColor);
-
- //draw dividing lines at median, 95% and 5%
- var iS = [0, 2, 4];
- var iSclass = ['', 'median', ''];
- var iSColor = [boxColor, boxInsideColor, boxColor];
- for (var i = 0; i < iS.length; i++) {
- boxplot
- .append('line')
- .attr('class', 'boxplot ' + iSclass[i])
- .attr('x1', horizontal ? x(0.5 - boxPlotWidth / 2) : x(probs[iS[i]]))
- .attr('x2', horizontal ? x(0.5 + boxPlotWidth / 2) : x(probs[iS[i]]))
- .attr('y1', horizontal ? y(probs[iS[i]]) : y(0.5 - boxPlotWidth / 2))
- .attr('y2', horizontal ? y(probs[iS[i]]) : y(0.5 + boxPlotWidth / 2))
- .style('fill', iSColor[i])
- .style('stroke', iSColor[i]);
- }
-
- //draw lines from 5% to 25% and from 75% to 95%
- var iS = [[0, 1], [3, 4]];
- for (var i = 0; i < iS.length; i++) {
- boxplot
- .append('line')
- .attr('class', 'boxplot')
- .attr('x1', horizontal ? x(0.5) : x(probs[iS[i][0]]))
- .attr('x2', horizontal ? x(0.5) : x(probs[iS[i][1]]))
- .attr('y1', horizontal ? y(probs[iS[i][0]]) : y(0.5))
- .attr('y2', horizontal ? y(probs[iS[i][1]]) : y(0.5))
- .style('stroke', boxColor);
- }
-
- boxplot
- .append('circle')
- .attr('class', 'boxplot mean')
- .attr('cx', horizontal ? x(0.5) : x(d3.mean(results)))
- .attr('cy', horizontal ? y(d3.mean(results)) : y(0.5))
- .attr('r', horizontal ? x(boxPlotWidth / 3) : y(1 - boxPlotWidth / 3))
- .style('fill', boxInsideColor)
- .style('stroke', boxColor);
-
- boxplot
- .append('circle')
- .attr('class', 'boxplot mean')
- .attr('cx', horizontal ? x(0.5) : x(d3.mean(results)))
- .attr('cy', horizontal ? y(d3.mean(results)) : y(0.5))
- .attr('r', horizontal ? x(boxPlotWidth / 6) : y(1 - boxPlotWidth / 6))
- .style('fill', boxColor)
- .style('stroke', 'None');
-
- var formatx = fmt ? d3.format(fmt) : d3.format('.2f');
-
- boxplot
- .selectAll('.boxplot')
- .append('title')
- .text(function(d) {
- return (
- 'N = ' +
- d.values.length +
- '\n' +
- 'Min = ' +
- d3.min(d.values) +
- '\n' +
- '5th % = ' +
- formatx(d3.quantile(d.values, 0.05)) +
- '\n' +
- 'Q1 = ' +
- formatx(d3.quantile(d.values, 0.25)) +
- '\n' +
- 'Median = ' +
- formatx(d3.median(d.values)) +
- '\n' +
- 'Q3 = ' +
- formatx(d3.quantile(d.values, 0.75)) +
- '\n' +
- '95th % = ' +
- formatx(d3.quantile(d.values, 0.95)) +
- '\n' +
- 'Max = ' +
- d3.max(d.values) +
- '\n' +
- 'Mean = ' +
- formatx(d3.mean(d.values)) +
- '\n' +
- 'StDev = ' +
- formatx(d3.deviation(d.values))
- );
- });
- }
-
- function addBoxPlots() {
- // Y-axis box plot
- var yValues = this.current_data.map(function(d) {
- return d.values.y;
- });
- var ybox = this.svg.append('g').attr('class', 'yMargin');
- drawBoxPlot(ybox, yValues, this.plot_height, 1, this.y_dom, 10, '#bbb', 'white');
- ybox.select('g.boxplot').attr(
- 'transform',
- 'translate(' + (this.plot_width + this.config.margin.right / 2) + ',0)'
- );
-
- //X-axis box plot
- var xValues = this.current_data.map(function(d) {
- return d.values.x;
- });
- var xbox = this.svg.append('g').attr('class', 'xMargin');
- drawBoxPlot(
- xbox, //svg element
- xValues, //values
- 1, //height
- this.plot_width, //width
- this.x_dom, //domain
- 10, //box plot width
- '#bbb', //box color
- 'white', //detail color
- '0.2f', //format
- false // horizontal?
- );
- xbox.select('g.boxplot').attr(
- 'transform',
- 'translate(0,' + -(this.config.margin.top / 2) + ')'
- );
- }
-
- function listVisits() {
- var _this = this;
-
- var possibleVisits = d3
- .set(
- this.initial_data
- .filter(function(f) {
- return f[_this.config.measure_col] === _this.config.measure;
- })
- .map(function(d) {
- return d[_this.config.visit_col];
- })
- )
- .values();
- possibleVisits.sort(function(a, b) {
- return _this.visits.indexOf(a) - _this.visits.indexOf(b);
- });
-
- this.wrap
- .select('.possible-visits')
- .text(
- this.config.measure +
- ' is collected at these visits: ' +
- possibleVisits.join(', ') +
- '.'
- );
- }
-
- function brushing() {
- var _this = this;
-
- var chart = this;
- var config = this.config;
- var extent = chart.brush.extent();
- var points = this.svg.selectAll('g.point').classed('selected', false);
- var decim = d3.format('.2f');
-
- points.select('circle').attr('fill-opacity', 0);
-
- var selected_points = points
- .filter(function(d) {
- var cx = _this.x(+d.values.x);
- var cy = _this.y(+d.values.y);
- return (
- extent[0][0] <= cx &&
- cx <= extent[1][0] &&
- extent[0][1] <= cy &&
- cy <= extent[1][1]
- );
- })
- .classed('selected', true)
- .select('circle')
- .attr('fill-opacity', this.config.marks[0].attributes['fill-opacity']);
-
- //redraw the table with the new data
-
- var selected_data = selected_points.data().map(function(m) {
- return m.values.raw[0];
- });
- chart.participantsSelected = selected_data.map(function(m) {
- return m.key;
- });
- selected_data.forEach(function(d) {
- d.shiftx = decim(d.shiftx);
- d.shifty = decim(d.shifty);
- d.chg = decim(d.chg);
- });
- this.listing.draw(selected_data);
- if (selected_data.length === 0) this.listing.wrap.style('display', 'none');
- else this.listing.wrap.style('display', 'block');
-
- //footnote
- this.wrap
- .select('.record-note')
- .style('text-align', 'right')
- .text('Details of ' + selected_data.length + ' selected points:');
- if (chart.brush.empty()) {
- this.wrap
- .select('.record-note')
- .style('text-align', 'center')
- .text('Click and drag to select points.');
- points
- .select('circle')
- .attr('fill-opacity', this.config.marks[0].attributes['fill-opacity']);
- }
- } //brushed
-
- function brushEnd() {
- this.events.participantsSelected.data = this.participantsSelected;
- this.wrap.node().dispatchEvent(this.events.participantsSelected);
- console.log("done brushin'");
- }
-
- function addBrush() {
- var chart = this;
- chart.brush = d3.svg
- .brush()
- .x(d3.scale.identity().domain(this.x.range()))
- .y(d3.scale.identity().domain(this.y.range()))
- .on('brush', brushing.bind(this))
- .on('brushend', brushEnd.bind(this));
-
- this.svg.call(chart.brush);
-
- this.svg.select('rect.extent').attr({
- 'shape-rendering': 'crispEdges',
- 'stroke-width': 1,
- stroke: '#ccc',
- 'fill-opacity': 0.1
- });
- }
-
- function addEqualityLine() {
- var overallMin = d3.min([this.x.domain()[0], this.y.domain()[0]]);
- var overallMax = d3.max([this.x.domain()[1], this.y.domain()[1]]);
-
- this.svg
- .append('line')
- .attr('x1', this.x(overallMin))
- .attr('x2', this.x(overallMax))
- .attr('y1', this.y(overallMin))
- .attr('y2', this.y(overallMax))
- .attr('stroke', 'black')
- .attr('clip-path', 'URL(#1)')
- .attr('class', 'identity');
- }
-
- function addTooltipsToAxisLabels() {
- this.svg
- .selectAll('.x.axis .axis-title')
- .append('title')
- .html(
- 'Baseline visit(s):
' +
- this.config.x_params.visits.join('
')
- );
- this.svg
- .selectAll('.y.axis .axis-title')
- .append('title')
- .html(
- 'Comparison visit(s):
' +
- this.config.y_params.visits.join('
')
- );
- }
-
- function onResize() {
- //Add univariate box plots to top and right margins.
- addBoxPlots.call(this);
-
- //Annotate list of visits at which measure has results.
- listVisits.call(this);
-
- //Expand the domains a bit so that points on the edge are brushable
- this.x_dom[0] = this.x_dom[0] < 0 ? this.x_dom[0] * 1.01 : this.x_dom[0] * 0.99;
- this.x_dom[1] = this.x_dom[1] < 0 ? this.x_dom[1] * 0.99 : this.x_dom[1] * 1.01;
- this.y_dom[0] = this.y_dom[0] < 0 ? this.y_dom[0] * 1.01 : this.y_dom[0] * 0.99;
- this.y_dom[1] = this.y_dom[1] < 0 ? this.y_dom[1] * 0.99 : this.y_dom[1] * 1.01;
-
- //Add brush functionality.
- addBrush.call(this);
-
- //add an equality line
- addEqualityLine.call(this);
-
- //Add tooltip to axis labels listing selected visits.
- addTooltipsToAxisLabels.call(this);
- }
-
- //polyfills
-
- function safetyShiftPlot(element, settings) {
- //settings
- if (settings.time_col && !settings.visit_col) settings.visit_col = settings.time_col; // prevent breaking backwards compatibility
- var mergedSettings = deepmerge_1(defaultSettings, settings, {
- arrayMerge: function arrayMerge(destination, source) {
- return source;
- }
- });
- var syncedSettings = syncSettings(clone(mergedSettings));
- var syncedControlInputs = syncControlInputs(clone(controlInputs), syncedSettings);
-
- //layout and styles
- defineLayout(element);
- defineStyles();
-
- //controls
- var controls = webcharts.createControls(
- document.querySelector(element).querySelector('#ssp-controls'),
- {
- location: 'top',
- inputs: syncedControlInputs
- }
- );
-
- //chart
- var chart = webcharts.createChart(
- document.querySelector(element).querySelector('#ssp-chart'),
- syncedSettings,
- controls
- );
- chart.on('init', onInit);
- chart.on('layout', onLayout);
- chart.on('preprocess', onPreprocess);
- chart.on('datatransform', onDataTransform);
- chart.on('draw', onDraw);
- chart.on('resize', onResize);
-
- //listing
- var listing = webcharts.createTable(
- document.querySelector(element).querySelector('#ssp-listing'),
- listingSettings
- );
- listing.init([]);
- chart.listing = listing;
-
- return chart;
- }
-
- return safetyShiftPlot;
-});
diff --git a/inst/htmlwidgets/lib/webcharts-1.11.6/webcharts.css b/inst/htmlwidgets/lib/webcharts-1.11.6/webcharts.css
deleted file mode 100644
index a64215a3..00000000
--- a/inst/htmlwidgets/lib/webcharts-1.11.6/webcharts.css
+++ /dev/null
@@ -1,635 +0,0 @@
-/*------------------------------------------------------------------------------------------------\
- Small Multiple Layout
-\------------------------------------------------------------------------------------------------*/
-
- div.wc-layout.wc-small-multiples::after {
- content: "";
- clear: both;
- display: block;
- }
-
- .wc-layout.wc-small-multiples > .wc-chart {
- float: left;
- padding: 0 2em 2em 0;
- }
-
- .wc-layout.wc-small-multiples > .wc-chart > .wc-chart-title {
- display: block;
- font-weight: bold;
- text-align: center;
- }
- .wc-small-multiples .wc-chart > .legend {
- display: none;
- }
-
-/*------------------------------------------------------------------------------------------------\
- Charts
-\------------------------------------------------------------------------------------------------*/
-
- .wc-chart {
- position: relative;
- font-family: 'Open Sans', Helvetica, Arial, sans-serif;
- }
-
- .wc-chart rect,
- .wc-chart line {
- shape-rendering: crispEdges;
- }
-
- /*next few are rules for d3 brushing stuff*/
- .wc-chart.brushable .overlay {
- cursor: crosshair;
- }
-
- .wc-chart rect.background {
- display: none;
- }
-
- .wc-chart rect.extent {
- fill: #ccc;
- fill-opacity: 0.4;
- shape-rendering: crispEdges;
- }
-
- .wc-chart .axis path.domain {
- fill: none;
- stroke: #ccc;
- shape-rendering: crispEdges;
- }
-
- .wc-chart .axis .tick line {
- stroke: #eee;
- shape-rendering: crispEdges;
- }
-
- .wc-chart .axis .tick text {
- font-size: .9em;
- }
-
- .wc-chart .axis .axis-title {
- fill: #555;
- }
-
- *[class*="wc-"] .legend {
- width: 100%;
- font-size: .9em;
- padding: 0;
- margin: 0;
- display: inline-block;
- }
-
- *[class*="wc-"] .legend .legend-title {
- font-weight: bold;
- margin-right: 1em;
- }
-
- *[class*="wc-"] .legend .legend-item {
- display: inline-block;
- margin-right: 1em;
- }
-
- *[class*="wc-"] .legend .legend-item .legend-color-block {
- position: relative;
- top: .2em;
- right: .25em;
- display: inline-block;
- }
- *[class*="wc-"] .legend .legend-item .legend-mark-text {
- font-weight: bold;
- margin-right: .5em;
- }
-
- .wc-chart .ordinal.axis .tick line,
- .wc-chart .ordinal.axis path {
- display: none;
- }
-
- .wc-chart.gridlines .ordinal.axis .tick line {
- display: block;
- }
-
-/*------------------------------------------------------------------------------------------------\
- Controls
-\------------------------------------------------------------------------------------------------*/
-
- .wc-controls {
- display: block;
- font-family: 'Open Sans', Helvetica, Arial, sans-serif;
- font-size: .9em;
- margin-bottom: 10px;
- }
-
- .wc-controls:empty {
- display: none;
- }
-
- .intro > .wc-controls {
- display: block;
- }
-
- .wc-controls .control-group {
- display: inline-table;
- max-width: 100%;
- margin: 0px 1em 1em 0;
- }
- .wc-controls.bottom .control-group {
- display: inline-table;
- }
-
- .wc-controls .control-group .wc-control-label {
- /*font-weight: bold;*/
- display: block;
- }
-
- .wc-controls .control-group.inline .wc-control-label {
- display: inline;
- margin-right: .5em;
- }
- .wc-controls .control-group.inline .changer {
- display: inline;
- margin-top: 0;
- }
-
- .wc-controls .control-group .wc-control-label + .changer {
- margin-top: 2px;
- }
-
- .wc-controls .control-group .wc-control-label.inline {
- display: inline;
- margin-right: .5em;
- }
-
- .wc-controls .control-group .wc-control-label .label-required {
- color: #de2d26;
- padding: 0 .25em;
- border: 1px solid;
- margin-left: .5em;
- }
-
- .wc-controls .span-description {
- display: block;
- font-size: .75em;
- color: #777;
- margin-bottom: 3px;
- }
- .wc-controls .span-description:empty {
- display: none;
- }
- .wc-controls .span-description.standout {
- font-style: italic;
- color: #d9534f;
- }
-
- .wc-controls .control-group label.radio,
- .wc-controls .control-group label.filter-values {
- display: inline-block;
- cursor: pointer;
- font-weight: normal;
- font-size: .9em;
- padding: 0;
- margin: 5px 10px 0 0;
- }
-
- .wc-controls .inline {
- display: inline;
- }
-
- .wc-controls .control-group input[type='text'],
- .wc-controls select {
- display: block;
- width: auto;
- max-width: 100%;
- padding: 0 2px;
- height: auto;
- border-radius: 0;
- }
-
- .wc-controls .control-group input[type='number'] {
- width: 70px;
- text-align: right;
- max-width: 100%;
- }
-
- .wc-controls .control-group input[type='checkbox'],
- .wc-controls .control-group input[type='radio'] {
- cursor: pointer;
- position: relative;
- top: .1em;
- float: none;
- margin: 0;
- }
-
- .wc-controls .control-group input[type='radio'] {
- vertical-align: bottom;
- margin-left: .25em;
- }
-
- .wc-controls .control-group input.inline {
- margin: 0 2px 2px 0;
- }
-
- .wc-controls .control-group .changer + .changer {
- margin-top: 2px;
- }
-
- .wc-controls .subsetter-ui {
- position: relative;
- display: inline-block;
- max-width: 100%;
- padding: 3px;
- border: 1px dashed #888;
- margin: 5px 5px 0 0;
- }
-
- .wc-controls .subsetter-ui .remove-btn {
- cursor: pointer;
- position: absolute;
- top: 2px;
- right: 2px;
- }
-
-/*------------------------------------------------------------------------------------------------\
- Tables
-\------------------------------------------------------------------------------------------------*/
-
- .wc-table {
- display: block;
- }
- .wc-table .hidden {
- display: none !important;
- }
- .wc-table .invisible {
- visibility: hidden !important;
- }
- .wc-table > * {
- display: block;
- }
- .wc-table .table-top {
- }
-
- .wc-table .table-bottom {
- }
-
- .wc-table .interactivity {
- display: inline-block;
- vertical-align: middle;
- margin: 10px 0;
- padding: 0;
- }
- .wc-table .interactivity .wc-button {
- display: inline-block;
- border: 2px solid gray;
- border-radius: 4px;
- padding: 2px 8px;
- margin: 0 2px;
- cursor: pointer;
- background: white;
- color: black;
- }
- .wc-table .interactivity .wc-button:hover {
- background: black;
- color: white;
- }
-
- /**-------------------------------------------------------------------------------------------\
- Searchable container
- \-------------------------------------------------------------------------------------------**/
-
- .wc-table .searchable-container {
- float: right;
- overflow: hidden;
- }
- .wc-table .searchable-container .nNrecords {
- }
- .wc-table .searchable-container input {
- margin: 0 10px 0 0;
- padding: 4px;
- }
-
- /**-------------------------------------------------------------------------------------------\
- Sortable container
- \-------------------------------------------------------------------------------------------**/
-
- .wc-table .sortable-container {
- margin-right: 10px;
- }
- .wc-table .sortable-container .instruction {
- margin-top: 4px;
- }
- .wc-table .sortable-container .sort-box {
- cursor: default;
- padding: 2px 4px;
- }
- .wc-table .sortable-container .sort-direction {
- font-weight: bold;
- margin: 3px;
- }
- .wc-table .sortable-container .sort-box .remove-sort {
- font-weight: bold;
- float: right;
- border: 1px solid gray;
- margin-top: 3px;
- padding: 2px 3px;
- font-size: 8px;
- background: white;
- color: red;
- }
- .wc-table .sortable-container .sort-box .remove-sort:hover {
- cursor: pointer;
- background: red;
- color: white;
- }
-
- /**-------------------------------------------------------------------------------------------\
- Table
- \-------------------------------------------------------------------------------------------**/
-
- .wc-table table {
- font-size: .9em;
- border-collapse: collapse;
- }
-
- /***--------------------------------------------------------------------------------------\
- Table header
- \--------------------------------------------------------------------------------------***/
-
- .wc-table table thead {
- }
- .wc-table table thead tr {
- }
- .wc-table table thead tr th {
- cursor: pointer;
- padding: 2px 5px;
- border-bottom: 2px solid black;
- text-align: left;
- }
- .wc-table table thead tr th:first-child {
- }
- .wc-table table thead tr th:last-child {
- }
-
- /***--------------------------------------------------------------------------------------\
- Table body
- \--------------------------------------------------------------------------------------***/
-
- .wc-table table tbody {
- }
- .wc-table table tbody tr {
- }
- .wc-table table tbody tr:nth-child(even) {
- background: #eee;
- }
- .wc-table table tbody tr:hover {
- background: #ccc;
- }
- .wc-table table tbody tr td {
- padding: 2px 5px;
- }
- .wc-table table tbody tr td:first-child {
- }
- .wc-table table tbody tr td:last-child {
- }
-
- .wc-table table tbody tr:last-child {
- border-bottom:1px solid black
- }
- .wc-table table tbody tr.no-data td {
- color: red;
- font-weight: bold;
- }
-
- /**-------------------------------------------------------------------------------------------\
- Pagination container
- \-------------------------------------------------------------------------------------------**/
-
- .wc-table .pagination-container {
- float: right;
- }
- .wc-table .pagination-container a {
- text-decoration: none;
- }
- .wc-table .pagination-container a:not(.active) {
- border: none;
- }
-
- /**-------------------------------------------------------------------------------------------\
- Exportable container
- \-------------------------------------------------------------------------------------------**/
-
- .wc-table .exportable-container {
- float: left;
- }
- .wc-table .exportable-container a {
- text-decoration: none;
- }
-
-/*------------------------------------------------------------------------------------------------\
- Miscellaneous
-\------------------------------------------------------------------------------------------------*/
-
- .loader {
- position:relative;
- width:20px;
- height:25px}
-
- .blockG {
- position:absolute;
- background-color:#eee;
- width:3px;
- height:8px;
- -moz-border-radius:4px 4px 0 0;
- -moz-transform:scale(0.4);
- -moz-animation-name:fadeG;
- -moz-animation-duration:0.48s;
- -moz-animation-iteration-count:infinite;
- -moz-animation-direction:linear;
- -webkit-border-radius:4px 4px 0 0;
- -webkit-transform:scale(0.4);
- -webkit-animation-name:fadeG;
- -webkit-animation-duration:0.48s;
- -webkit-animation-iteration-count:infinite;
- -webkit-animation-direction:linear;
- -ms-border-radius:4px 4px 0 0;
- -ms-transform:scale(0.4);
- -ms-animation-name:fadeG;
- -ms-animation-duration:0.48s;
- -ms-animation-iteration-count:infinite;
- -ms-animation-direction:linear;
- -o-border-radius:4px 4px 0 0;
- -o-transform:scale(0.4);
- -o-animation-name:fadeG;
- -o-animation-duration:0.48s;
- -o-animation-iteration-count:infinite;
- -o-animation-direction:linear;
- border-radius:4px 4px 0 0;
- transform:scale(0.4);
- animation-name:fadeG;
- animation-duration:0.48s;
- animation-iteration-count:infinite;
- animation-direction:linear;
- }
-
- .rotate1 {
- left:0;
- top:9px;
- -moz-animation-delay:0.18s;
- -moz-transform:rotate(-90deg);
- -webkit-animation-delay:0.18s;
- -webkit-transform:rotate(-90deg);
- -ms-animation-delay:0.18s;
- -ms-transform:rotate(-90deg);
- -o-animation-delay:0.18s;
- -o-transform:rotate(-90deg);
- animation-delay:0.18s;
- transform:rotate(-90deg);
- }
-
- .rotate2 {
- left:3px;
- top:3px;
- -moz-animation-delay:0.24s;
- -moz-transform:rotate(-45deg);
- -webkit-animation-delay:0.24s;
- -webkit-transform:rotate(-45deg);
- -ms-animation-delay:0.24s;
- -ms-transform:rotate(-45deg);
- -o-animation-delay:0.24s;
- -o-transform:rotate(-45deg);
- animation-delay:0.24s;
- transform:rotate(-45deg);
- }
-
- .rotate3 {
- left:8px;
- top:1px;
- -moz-animation-delay:0.3s;
- -moz-transform:rotate(0deg);
- -webkit-animation-delay:0.3s;
- -webkit-transform:rotate(0deg);
- -ms-animation-delay:0.3s;
- -ms-transform:rotate(0deg);
- -o-animation-delay:0.3s;
- -o-transform:rotate(0deg);
- animation-delay:0.3s;
- transform:rotate(0deg);
- }
-
- .rotate4 {
- right:3px;
- top:3px;
- -moz-animation-delay:0.36s;
- -moz-transform:rotate(45deg);
- -webkit-animation-delay:0.36s;
- -webkit-transform:rotate(45deg);
- -ms-animation-delay:0.36s;
- -ms-transform:rotate(45deg);
- -o-animation-delay:0.36s;
- -o-transform:rotate(45deg);
- animation-delay:0.36s;
- transform:rotate(45deg);
- }
-
- .rotate5 {
- right:0;
- top:9px;
- -moz-animation-delay:0.42000000000000004s;
- -moz-transform:rotate(90deg);
- -webkit-animation-delay:0.42000000000000004s;
- -webkit-transform:rotate(90deg);
- -ms-animation-delay:0.42000000000000004s;
- -ms-transform:rotate(90deg);
- -o-animation-delay:0.42000000000000004s;
- -o-transform:rotate(90deg);
- animation-delay:0.42000000000000004s;
- transform:rotate(90deg);
- }
-
- .rotate6 {
- right:3px;
- bottom:2px;
- -moz-animation-delay:0.48s;
- -moz-transform:rotate(135deg);
- -webkit-animation-delay:0.48s;
- -webkit-transform:rotate(135deg);
- -ms-animation-delay:0.48s;
- -ms-transform:rotate(135deg);
- -o-animation-delay:0.48s;
- -o-transform:rotate(135deg);
- animation-delay:0.48s;
- transform:rotate(135deg);
- }
-
- .rotate7 {
- bottom:0;
- left:8px;
- -moz-animation-delay:0.5399999999999999s;
- -moz-transform:rotate(180deg);
- -webkit-animation-delay:0.5399999999999999s;
- -webkit-transform:rotate(180deg);
- -ms-animation-delay:0.5399999999999999s;
- -ms-transform:rotate(180deg);
- -o-animation-delay:0.5399999999999999s;
- -o-transform:rotate(180deg);
- animation-delay:0.5399999999999999s;
- transform:rotate(180deg);
- }
-
- .rotate8 {
- left:3px;
- bottom:2px;
- -moz-animation-delay:0.6s;
- -moz-transform:rotate(-135deg);
- -webkit-animation-delay:0.6s;
- -webkit-transform:rotate(-135deg);
- -ms-animation-delay:0.6s;
- -ms-transform:rotate(-135deg);
- -o-animation-delay:0.6s;
- -o-transform:rotate(-135deg);
- animation-delay:0.6s;
- transform:rotate(-135deg);
- }
-
- @-moz-keyframes fadeG {
- 0% {
- background-color:#000000}
-
- 100% {
- background-color:#eee}
-
- }
-
- @-webkit-keyframes fadeG {
- 0% {
- background-color:#000000}
-
- 100% {
- background-color:#eee}
-
- }
-
- @-ms-keyframes fadeG {
- 0% {
- background-color:#000000}
-
- 100% {
- background-color:#eee}
-
- }
-
- @-o-keyframes fadeG {
- 0% {
- background-color:#000000}
-
- 100% {
- background-color:#eee}
-
- }
-
- @keyframes fadeG {
- 0% {
- background-color:#000000}
-
- 100% {
- background-color:#eee}
-
- }
diff --git a/inst/htmlwidgets/lib/webcharts-1.11.6/webcharts.js b/inst/htmlwidgets/lib/webcharts-1.11.6/webcharts.js
deleted file mode 100644
index b624c116..00000000
--- a/inst/htmlwidgets/lib/webcharts-1.11.6/webcharts.js
+++ /dev/null
@@ -1,4542 +0,0 @@
-(function(global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined'
- ? (module.exports = factory(require('d3')))
- : typeof define === 'function' && define.amd
- ? define(['d3'], factory)
- : (global.webCharts = factory(global.d3));
-})(typeof self !== 'undefined' ? self : this, function(d3) {
- 'use strict';
- var version = '1.11.6';
-
- function init(data) {
- var _this = this;
-
- var test = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
-
- if (d3.select(this.div).select('.loader').empty()) {
- d3
- .select(this.div)
- .insert('div', ':first-child')
- .attr('class', 'loader')
- .selectAll('.blockG')
- .data(d3.range(8))
- .enter()
- .append('div')
- .attr('class', function(d) {
- return 'blockG rotate' + (d + 1);
- });
- }
-
- this.wrap.attr('class', 'wc-chart');
-
- this.setDefaults();
-
- this.raw_data = data;
- this.initial_data = data;
-
- var startup = function startup(data) {
- //connect this chart and its controls, if any
- if (_this.controls) {
- _this.controls.targets.push(_this);
- if (!_this.controls.ready) {
- _this.controls.init(_this.raw_data);
- } else {
- _this.controls.layout();
- }
- }
-
- //make sure container is visible (has height and width) before trying to initialize
- var visible = d3.select(_this.div).property('offsetWidth') > 0 || test;
- if (!visible) {
- console.warn(
- 'The chart cannot be initialized inside an element with 0 width. The chart will be initialized as soon as the container element is given a width > 0.'
- );
- var onVisible = setInterval(function(i) {
- var visible_now = d3.select(_this.div).property('offsetWidth') > 0;
- if (visible_now) {
- _this.layout();
- _this.draw();
- clearInterval(onVisible);
- }
- }, 500);
- } else {
- _this.layout();
- _this.draw();
- }
- };
-
- this.events.onInit.call(this);
- if (this.raw_data.length) {
- this.checkRequired(this.raw_data);
- }
- startup(data);
-
- return this;
- }
-
- function checkRequired(data) {
- var _this = this;
-
- var colnames = Object.keys(data[0]);
- var requiredVars = [];
- var requiredCols = [];
- if (this.config.x && this.config.x.column) {
- requiredVars.push('this.config.x.column');
- requiredCols.push(this.config.x.column);
- }
- if (this.config.y && this.config.y.column) {
- requiredVars.push('this.config.y.column');
- requiredCols.push(this.config.y.column);
- }
- if (this.config.color_by) {
- requiredVars.push('this.config.color_by');
- requiredCols.push(this.config.color_by);
- }
- if (this.config.marks)
- this.config.marks.forEach(function(e, i) {
- if (e.per && e.per.length) {
- e.per.forEach(function(p, j) {
- requiredVars.push('this.config.marks[' + i + '].per[' + j + ']');
- requiredCols.push(p);
- });
- }
- if (e.split) {
- requiredVars.push('this.config.marks[' + i + '].split');
- requiredCols.push(e.split);
- }
- if (e.values) {
- for (var value in e.values) {
- requiredVars.push('this.config.marks[' + i + "].values['" + value + "']");
- requiredCols.push(value);
- }
- }
- });
-
- var missingDataField = false;
- requiredCols.forEach(function(e, i) {
- if (colnames.indexOf(e) < 0) {
- missingDataField = true;
- d3.select(_this.div).select('.loader').remove();
- _this.wrap
- .append('div')
- .style('color', 'red')
- .html(
- 'The value "' +
- e +
- '" for the ' +
- requiredVars[i] +
- ' setting does not match any column in the provided dataset.'
- );
- throw new Error(
- 'Error in settings object: The value "' +
- e +
- '" for the ' +
- requiredVars[i] +
- ' setting does not match any column in the provided dataset.'
- );
- }
- });
-
- return {
- missingDataField: missingDataField,
- dataFieldArguments: requiredVars,
- requiredDataFields: requiredCols
- };
- }
-
- function addSVG() {
- this.svg = this.wrap
- .append('svg')
- .datum(function() {
- return null;
- }) // prevent data inheritance
- .attr({
- class: 'wc-svg',
- xmlns: 'http://www.w3.org/2000/svg',
- version: '1.1',
- xlink: 'http://www.w3.org/1999/xlink'
- })
- .append('g')
- .style('display', 'inline-block');
- }
-
- function addDefs() {
- var defs = this.svg.append('defs');
-
- //Add pattern.
- defs
- .append('pattern')
- .attr({
- id: 'diagonal-stripes',
- x: 0,
- y: 0,
- width: 3,
- height: 8,
- patternUnits: 'userSpaceOnUse',
- patternTransform: 'rotate(30)'
- })
- .append('rect')
- .attr({
- x: '0',
- y: '0',
- width: '2',
- height: '8'
- })
- .style({
- stroke: 'none',
- fill: 'black'
- });
-
- //Add clipPath.
- defs.append('clipPath').attr('id', this.id).append('rect').attr('class', 'plotting-area');
- }
-
- function addXAxis() {
- this.svg
- .append('g')
- .attr('class', 'x axis')
- .append('text')
- .attr('class', 'axis-title')
- .attr('dy', '-.35em')
- .attr('text-anchor', 'middle');
- }
-
- function addYAxis() {
- this.svg
- .append('g')
- .attr('class', 'y axis')
- .append('text')
- .attr('class', 'axis-title')
- .attr('transform', 'rotate(-90)')
- .attr('dy', '.75em')
- .attr('text-anchor', 'middle');
- }
-
- function addOverlay() {
- this.overlay = this.svg
- .append('rect')
- .attr('class', 'overlay')
- .attr('opacity', 0)
- .attr('fill', 'none')
- .style('pointer-events', 'all');
- }
-
- function addLegend() {
- //The legend is contained in the parent object of multiples so each multiple does not need its own legend.
- if (!this.parent)
- this.wrap
- .append('ul')
- .datum(function() {
- return null;
- }) // prevent data inheritance
- .attr('class', 'legend')
- .style('vertical-align', 'top')
- .append('span')
- .attr('class', 'legend-title');
- }
-
- function clearLoader() {
- d3.select(this.div).select('.loader').remove();
- }
-
- function layout() {
- addSVG.call(this);
- addDefs.call(this);
- addXAxis.call(this);
- addYAxis.call(this);
- addOverlay.call(this);
- addLegend.call(this);
- clearLoader.call(this);
- this.events.onLayout.call(this);
- }
-
- function draw(raw_data, processed_data) {
- var _this = this;
-
- var chart = this;
- var config = this.config;
-
- //if pre-processing callback, run it now
- this.events.onPreprocess.call(this);
-
- /////////////////////////
- // Data prep pipeline //
- /////////////////////////
-
- // if user passed raw_data to chart.draw(), use that, otherwise use chart.raw_data
- var raw = raw_data ? raw_data : this.raw_data ? this.raw_data : [];
-
- // warn the user about the perils of "processed_data"
- if (processed_data) {
- console.warn(
- "Drawing the chart using user-defined 'processed_data', this is an experimental, untested feature."
- );
- }
-
- //Call consolidateData - this applies filters from controls and prepares data for each set of marks.
- this.consolidateData(raw);
-
- /////////////////////////////
- // Prepare scales and axes //
- /////////////////////////////
-
- var div_width = parseInt(this.wrap.style('width'));
-
- this.setColorScale();
-
- var max_width = config.max_width ? config.max_width : div_width;
- this.raw_width = config.x.type === 'ordinal' && +config.x.range_band
- ? (+config.x.range_band + config.x.range_band * config.padding) * this.x_dom.length
- : config.resizable ? max_width : config.width ? config.width : div_width;
- this.raw_height = config.y.type === 'ordinal' && +config.y.range_band
- ? (+config.y.range_band + config.y.range_band * config.padding) * this.y_dom.length
- : config.resizable
- ? max_width * (1 / config.aspect)
- : config.height ? config.height : div_width * (1 / config.aspect);
-
- var pseudo_width = this.svg.select('.overlay').attr('width')
- ? this.svg.select('.overlay').attr('width')
- : this.raw_width;
- var pseudo_height = this.svg.select('.overlay').attr('height')
- ? this.svg.select('.overlay').attr('height')
- : this.raw_height;
-
- this.svg.select('.x.axis').select('.axis-title').text(function(d) {
- return typeof config.x.label === 'string'
- ? config.x.label
- : typeof config.x.label === 'function' ? config.x.label.call(_this) : null;
- });
- this.svg.select('.y.axis').select('.axis-title').text(function(d) {
- return typeof config.y.label === 'string'
- ? config.y.label
- : typeof config.y.label === 'function' ? config.y.label.call(_this) : null;
- });
-
- this.xScaleAxis(pseudo_width);
- this.yScaleAxis(pseudo_height);
-
- if (config.resizable && typeof window !== 'undefined') {
- d3.select(window).on('resize.' + this.element + this.id, function() {
- chart.resize();
- });
- } else if (typeof window !== 'undefined') {
- d3.select(window).on('resize.' + this.element + this.id, null);
- }
-
- this.events.onDraw.call(this);
-
- //////////////////////////////////////////////////////////////////////
- // Call resize - updates marks on the chart (amongst other things) //
- /////////////////////////////////////////////////////////////////////
- this.resize();
- }
-
- function naturalSorter(a, b) {
- //adapted from http://www.davekoelle.com/files/alphanum.js
- function chunkify(t) {
- var tz = [];
- var x = 0,
- y = -1,
- n = 0,
- i = void 0,
- j = void 0;
-
- while ((i = (j = t.charAt(x++)).charCodeAt(0))) {
- var m = i == 46 || (i >= 48 && i <= 57);
- if (m !== n) {
- tz[++y] = '';
- n = m;
- }
- tz[y] += j;
- }
- return tz;
- }
-
- var aa = chunkify(a.toLowerCase());
- var bb = chunkify(b.toLowerCase());
-
- for (var x = 0; aa[x] && bb[x]; x++) {
- if (aa[x] !== bb[x]) {
- var c = Number(aa[x]),
- d = Number(bb[x]);
- if (c == aa[x] && d == bb[x]) {
- return c - d;
- } else {
- return aa[x] > bb[x] ? 1 : -1;
- }
- }
- }
-
- return aa.length - bb.length;
- }
-
- function setDomain(axis) {
- var _this = this;
-
- var otherAxis = axis === 'x' ? 'y' : 'x';
-
- if (this.config[axis].type === 'ordinal') {
- //ordinal domains
- if (this.config[axis].domain) {
- //user-defined domain
- this[axis + '_dom'] = this.config[axis].domain;
- } else if (this.config[axis].order) {
- //data-driven domain with user-defined domain order
- this[axis + '_dom'] = d3
- .set(
- d3.merge(
- this.marks.map(function(mark) {
- return mark[axis + '_dom'];
- })
- )
- )
- .values()
- .sort(function(a, b) {
- return d3.ascending(
- _this.config[axis].order.indexOf(a),
- _this.config[axis].order.indexOf(b)
- );
- });
- } else if (
- this.config[axis].sort &&
- this.config[axis].sort === 'alphabetical-ascending'
- ) {
- //data-driven domain with user-defined domain sort algorithm that sorts the axis
- //alphanumerically, first to last
- this[axis + '_dom'] = d3
- .set(
- d3.merge(
- this.marks.map(function(mark) {
- return mark[axis + '_dom'];
- })
- )
- )
- .values()
- .sort(naturalSorter);
- } else if (
- ['time', 'linear'].indexOf(this.config[otherAxis].type) > -1 &&
- this.config[axis].sort === 'earliest'
- ) {
- //data-driven domain plotted against a time or linear axis that sorts the axis values
- //by earliest event/datum; generally used with timeline charts
- this[axis + '_dom'] = d3
- .nest()
- .key(function(d) {
- return d[_this.config[axis].column];
- })
- .rollup(function(d) {
- return d
- .map(function(m) {
- return m[_this.config[otherAxis].column];
- })
- .filter(function(f) {
- return f instanceof Date;
- });
- })
- .entries(this.filtered_data)
- .sort(function(a, b) {
- return d3.min(b.values) - d3.min(a.values);
- })
- .map(function(m) {
- return m.key;
- });
- } else if (
- !this.config[axis].sort ||
- this.config[axis].sort === 'alphabetical-descending'
- ) {
- //data-driven domain with default/user-defined domain sort algorithm that sorts the
- //axis alphanumerically, last to first
- this[axis + '_dom'] = d3
- .set(
- d3.merge(
- this.marks.map(function(mark) {
- return mark[axis + '_dom'];
- })
- )
- )
- .values()
- .sort(naturalSorter)
- .reverse();
- } else {
- //data-driven domain with an invalid user-defined sort algorithm that captures a unique
- //set of values as they appear in the data
- this[axis + '_dom'] = d3
- .set(
- d3.merge(
- this.marks.map(function(mark) {
- return mark[axis + '_dom'];
- })
- )
- )
- .values();
- }
- } else if (
- this.config.marks
- .map(function(m) {
- return m['summarize' + axis.toUpperCase()] === 'percent';
- })
- .indexOf(true) > -1
- ) {
- //rate domains run from 0 to 1
- this[axis + '_dom'] = [0, 1];
- } else {
- //continuous domains run from the minimum to the maximum raw (or is it summarized...?) value
- //TODO: they should really run from the minimum to the maximum summarized value, e.g. a
- //TODO: means over time chart should plot over the range of the means, not the range of the
- //TODO: raw data
- this[axis + '_dom'] = d3.extent(
- d3.merge(
- this.marks.map(function(mark) {
- return mark[axis + '_dom'];
- })
- )
- );
- }
-
- //Give the domain a range when the range of the variable is 0.
- if (
- this.config[axis].type === 'linear' &&
- this[axis + '_dom'][0] === this[axis + '_dom'][1]
- )
- this[axis + '_dom'] = this[axis + '_dom'][0] !== 0
- ? [
- this[axis + '_dom'][0] - this[axis + '_dom'][0] * 0.01,
- this[axis + '_dom'][1] + this[axis + '_dom'][1] * 0.01
- ]
- : [-1, 1];
-
- return this[axis + '_dom'];
- }
-
- function consolidateData(raw) {
- var _this = this;
-
- this.setDefaults();
-
- //Apply filters from associated controls objects to raw data.
- this.filtered_data = raw;
- if (this.filters.length) {
- this.filters.forEach(function(filter) {
- _this.filtered_data = _this.filtered_data.filter(function(d) {
- return filter.all === true && filter.index === 0
- ? d
- : filter.val instanceof Array
- ? filter.val.indexOf(d[filter.col]) > -1
- : d[filter.col] === filter.val;
- });
- });
- }
-
- //Summarize data for each mark.
- this.config.marks.forEach(function(mark, i) {
- if (mark.type !== 'bar') {
- mark.arrange = null;
- mark.split = null;
- }
-
- var mark_info = mark.per
- ? _this.transformData(raw, mark)
- : {
- data: [],
- x_dom: [],
- y_dom: []
- };
-
- _this.marks[i] = Object.assign({}, mark, mark_info);
- });
-
- //Set domains given extents of summarized mark data.
- setDomain.call(this, 'x');
- setDomain.call(this, 'y');
- }
-
- function setDefaults() {
- this.config.x = this.config.x || {};
- this.config.y = this.config.y || {};
-
- this.config.x.label = this.config.x.label !== undefined
- ? this.config.x.label
- : this.config.x.column;
- this.config.y.label = this.config.y.label !== undefined
- ? this.config.y.label
- : this.config.y.column;
-
- this.config.x.sort = this.config.x.sort || 'alphabetical-ascending';
- this.config.y.sort = this.config.y.sort || 'alphabetical-descending';
-
- this.config.x.type = this.config.x.type || 'linear';
- this.config.y.type = this.config.y.type || 'linear';
-
- this.config.x.range_band = this.config.x.range_band || this.config.range_band;
- this.config.y.range_band = this.config.y.range_band || this.config.range_band;
-
- this.config.margin = this.config.margin || {};
- this.config.legend = this.config.legend || {};
- this.config.legend.label = this.config.legend.label !== undefined
- ? this.config.legend.label
- : this.config.color_by;
- this.config.legend.location = this.config.legend.location !== undefined
- ? this.config.legend.location
- : 'bottom';
- this.config.marks = this.config.marks && this.config.marks.length
- ? this.config.marks
- : [{}];
- this.config.marks.forEach(function(m, i) {
- m.id = m.id ? m.id : 'mark' + (i + 1);
- });
-
- this.config.date_format = this.config.date_format || '%x';
-
- this.config.padding = this.config.padding !== undefined ? this.config.padding : 0.3;
- this.config.outer_pad = this.config.outer_pad !== undefined ? this.config.outer_pad : 0.1;
-
- this.config.resizable = this.config.resizable !== undefined ? this.config.resizable : true;
-
- this.config.aspect = this.config.aspect || 1.33;
-
- this.config.colors = this.config.colors || [
- 'rgb(102,194,165)',
- 'rgb(252,141,98)',
- 'rgb(141,160,203)',
- 'rgb(231,138,195)',
- 'rgb(166,216,84)',
- 'rgb(255,217,47)',
- 'rgb(229,196,148)',
- 'rgb(179,179,179)'
- ];
-
- this.config.scale_text = this.config.scale_text === undefined
- ? true
- : this.config.scale_text;
- this.config.transitions = this.config.transitions === undefined
- ? true
- : this.config.transitions;
- }
-
- function cleanData(mark, raw) {
- var _this = this;
-
- var dateConvert = d3.time.format(this.config.date_format);
- var clean = raw;
- // only use data for the current mark
- clean = mark.per && mark.per.length
- ? clean.filter(function(f) {
- return f[mark.per[0]] !== undefined;
- })
- : clean;
-
- // Make sure data has x and y values
- if (this.config.x.column) {
- clean = clean.filter(function(f) {
- return [undefined, null].indexOf(f[_this.config.x.column]) < 0;
- });
- }
- if (this.config.y.column) {
- clean = clean.filter(function(f) {
- return [undefined, null].indexOf(f[_this.config.y.column]) < 0;
- });
- }
-
- //check that x and y have the correct formats
- if (this.config.x.type === 'time') {
- clean = clean.filter(function(f) {
- return f[_this.config.x.column] instanceof Date
- ? f[_this.config.x.column]
- : dateConvert.parse(f[_this.config.x.column]);
- });
- clean.forEach(function(e) {
- return (e[_this.config.x.column] = e[_this.config.x.column] instanceof Date
- ? e[_this.config.x.column]
- : dateConvert.parse(e[_this.config.x.column]));
- });
- }
- if (this.config.y.type === 'time') {
- clean = clean.filter(function(f) {
- return f[_this.config.y.column] instanceof Date
- ? f[_this.config.y.column]
- : dateConvert.parse(f[_this.config.y.column]);
- });
- clean.forEach(function(e) {
- return (e[_this.config.y.column] = e[_this.config.y.column] instanceof Date
- ? e[_this.config.y.column]
- : dateConvert.parse(e[_this.config.y.column]));
- });
- }
-
- if (
- (this.config.x.type === 'linear' || this.config.x.type === 'log') &&
- this.config.x.column
- ) {
- clean = clean.filter(function(f) {
- return mark.summarizeX !== 'count' && mark.summarizeX !== 'percent'
- ? !(isNaN(f[_this.config.x.column]) || /^\s*$/.test(f[_this.config.x.column])) // is or coerces to a number and is not a string that coerces to 0
- : f;
- });
- }
- if (
- (this.config.y.type === 'linear' || this.config.y.type === 'log') &&
- this.config.y.column
- ) {
- clean = clean.filter(function(f) {
- return mark.summarizeY !== 'count' && mark.summarizeY !== 'percent'
- ? !(isNaN(f[_this.config.y.column]) || /^\s*$/.test(f[_this.config.y.column])) // is or coerces to a number and is not a string that coerces to 0
- : f;
- });
- }
-
- return clean;
- }
-
- var stats = {
- mean: d3.mean,
- min: d3.min,
- max: d3.max,
- median: d3.median,
- sum: d3.sum
- };
-
- function summarize(vals) {
- var operation = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'mean';
-
- var nvals = vals
- .filter(function(f) {
- return +f || +f === 0;
- })
- .map(function(m) {
- return +m;
- });
-
- if (operation === 'cumulative') {
- return null;
- }
-
- var mathed = operation === 'count'
- ? vals.length
- : operation === 'percent' ? vals.length : stats[operation](nvals);
-
- return mathed;
- }
-
- function makeNest(mark, entries, sublevel) {
- var _this = this;
-
- var dom_xs = [];
- var dom_ys = [];
- var this_nest = d3.nest();
- var totalOrder = void 0;
-
- if (
- (this.config.x.type === 'linear' && this.config.x.bin) ||
- (this.config.y.type === 'linear' && this.config.y.bin)
- ) {
- var xy = this.config.x.type === 'linear' && this.config.x.bin ? 'x' : 'y';
- mark.quant = d3.scale
- .quantile()
- .domain(
- this.config[xy].domain
- ? this.config[xy].domain
- : d3.extent(
- entries.map(function(m) {
- return +m[_this.config[xy].column];
- })
- )
- )
- .range(d3.range(+this.config[xy].bin));
-
- entries.forEach(function(e) {
- return (e.wc_bin = mark.quant(e[_this.config[xy].column]));
- });
-
- this_nest.key(function(d) {
- return mark.quant.invertExtent(d.wc_bin);
- });
- } else {
- this_nest.key(function(d) {
- return mark.per
- .map(function(m) {
- return d[m];
- })
- .join(' ');
- });
- }
-
- if (sublevel) {
- this_nest.key(function(d) {
- return d[sublevel];
- });
- this_nest.sortKeys(function(a, b) {
- return _this.config.x.type === 'time'
- ? d3.ascending(new Date(a), new Date(b))
- : _this.config.x.order
- ? d3.ascending(
- _this.config.x.order.indexOf(a),
- _this.config.x.order.indexOf(b)
- )
- : sublevel === _this.config.color_by && _this.config.legend.order
- ? d3.ascending(
- _this.config.legend.order.indexOf(a),
- _this.config.legend.order.indexOf(b)
- )
- : _this.config.x.type === 'ordinal' || _this.config.y.type === 'ordinal'
- ? naturalSorter(a, b)
- : d3.ascending(+a, +b);
- });
- }
- this_nest.rollup(function(r) {
- var obj = { raw: r };
- var y_vals = r
- .map(function(m) {
- return m[_this.config.y.column];
- })
- .sort(d3.ascending);
- var x_vals = r
- .map(function(m) {
- return m[_this.config.x.column];
- })
- .sort(d3.ascending);
- obj.x = _this.config.x.type === 'ordinal'
- ? r[0][_this.config.x.column]
- : summarize(x_vals, mark.summarizeX);
- obj.y = _this.config.y.type === 'ordinal'
- ? r[0][_this.config.y.column]
- : summarize(y_vals, mark.summarizeY);
-
- obj.x_q25 = _this.config.error_bars && _this.config.y.type === 'ordinal'
- ? d3.quantile(x_vals, 0.25)
- : obj.x;
- obj.x_q75 = _this.config.error_bars && _this.config.y.type === 'ordinal'
- ? d3.quantile(x_vals, 0.75)
- : obj.x;
- obj.y_q25 = _this.config.error_bars ? d3.quantile(y_vals, 0.25) : obj.y;
- obj.y_q75 = _this.config.error_bars ? d3.quantile(y_vals, 0.75) : obj.y;
- dom_xs.push([obj.x_q25, obj.x_q75, obj.x]);
- dom_ys.push([obj.y_q25, obj.y_q75, obj.y]);
-
- if (mark.summarizeY === 'cumulative') {
- var interm = entries.filter(function(f) {
- return _this.config.x.type === 'time'
- ? new Date(f[_this.config.x.column]) <=
- new Date(r[0][_this.config.x.column])
- : +f[_this.config.x.column] <= +r[0][_this.config.x.column];
- });
- if (mark.per.length) {
- interm = interm.filter(function(f) {
- return f[mark.per[0]] === r[0][mark.per[0]];
- });
- }
-
- var cumul = _this.config.x.type === 'time'
- ? interm.length
- : d3.sum(
- interm.map(function(m) {
- return +m[_this.config.y.column] || +m[_this.config.y.column] === 0
- ? +m[_this.config.y.column]
- : 1;
- })
- );
- dom_ys.push([cumul]);
- obj.y = cumul;
- }
- if (mark.summarizeX === 'cumulative') {
- var _interm = entries.filter(function(f) {
- return _this.config.y.type === 'time'
- ? new Date(f[_this.config.y.column]) <=
- new Date(r[0][_this.config.y.column])
- : +f[_this.config.y.column] <= +r[0][_this.config.y.column];
- });
- if (mark.per.length) {
- _interm = _interm.filter(function(f) {
- return f[mark.per[0]] === r[0][mark.per[0]];
- });
- }
- dom_xs.push([_interm.length]);
- obj.x = _interm.length;
- }
-
- return obj;
- });
-
- var test = this_nest.entries(entries);
-
- var dom_x = d3.extent(d3.merge(dom_xs));
- var dom_y = d3.extent(d3.merge(dom_ys));
-
- if (sublevel && mark.type === 'bar' && mark.split) {
- //calculate percentages in bars
- test.forEach(function(e) {
- var axis = _this.config.x.type === 'ordinal' ||
- (_this.config.x.type === 'linear' && _this.config.x.bin)
- ? 'y'
- : 'x';
- e.total = d3.sum(
- e.values.map(function(m) {
- return +m.values[axis];
- })
- );
- var counter = 0;
- e.values.forEach(function(v, i) {
- if (
- _this.config.x.type === 'ordinal' ||
- (_this.config.x.type === 'linear' && _this.config.x.bin)
- ) {
- v.values.y = mark.summarizeY === 'percent'
- ? v.values.y / e.total
- : v.values.y || 0;
- counter += +v.values.y;
- v.values.start = e.values[i - 1] ? counter : v.values.y;
- } else {
- v.values.x = mark.summarizeX === 'percent'
- ? v.values.x / e.total
- : v.values.x || 0;
- v.values.start = counter;
- counter += +v.values.x;
- }
- });
- });
-
- if (mark.arrange === 'stacked') {
- if (
- this.config.x.type === 'ordinal' ||
- (this.config.x.type === 'linear' && this.config.x.bin)
- ) {
- dom_y = d3.extent(
- test.map(function(m) {
- return m.total;
- })
- );
- }
- if (
- this.config.y.type === 'ordinal' ||
- (this.config.y.type === 'linear' && this.config.y.bin)
- ) {
- dom_x = d3.extent(
- test.map(function(m) {
- return m.total;
- })
- );
- }
- }
- } else {
- var axis = this.config.x.type === 'ordinal' ||
- (this.config.x.type === 'linear' && this.config.x.bin)
- ? 'y'
- : 'x';
- test.forEach(function(e) {
- return (e.total = e.values[axis]);
- });
- }
-
- if (
- (this.config.x.sort === 'total-ascending' && this.config.x.type == 'ordinal') ||
- (this.config.y.sort === 'total-descending' && this.config.y.type == 'ordinal')
- ) {
- totalOrder = test
- .sort(function(a, b) {
- return d3.ascending(a.total, b.total);
- })
- .map(function(m) {
- return m.key;
- });
- } else if (
- (this.config.x.sort === 'total-descending' && this.config.x.type == 'ordinal') ||
- (this.config.y.sort === 'total-ascending' && this.config.y.type == 'ordinal')
- ) {
- totalOrder = test
- .sort(function(a, b) {
- return d3.descending(+a.total, +b.total);
- })
- .map(function(m) {
- return m.key;
- });
- }
-
- return { nested: test, dom_x: dom_x, dom_y: dom_y, totalOrder: totalOrder };
- }
-
- //////////////////////////////////////////////////////////
- // transformData(raw, mark) provides specifications and data for
- // each set of marks. As such, it is called once for each
- // item specified in the config.marks array.
- //
- // parameters
- // raw - the raw data for use in the mark. Filters from controls
- // are typically already applied.
- // mark - a single mark object from config.marks
- ////////////////////////////////////////////////////////
-
- function transformData(raw, mark) {
- var _this = this;
-
- //convenience mappings
- var config = this.config;
- var x_behavior = config.x.behavior || 'raw';
- var y_behavior = config.y.behavior || 'raw';
- var sublevel = mark.type === 'line'
- ? config.x.column
- : mark.type === 'bar' && mark.split ? mark.split : null;
-
- //////////////////////////////////////////////////////////////////////////////////
- // DATA PREP
- // prepare data based on the properties of the mark - drop missing records, etc
- //////////////////////////////////////////////////////////////////////////////////
- var cleaned = cleanData.call(this, mark, raw);
-
- //prepare nested data required for bar charts
- var raw_nest = void 0;
- if (mark.type === 'bar') {
- raw_nest = mark.arrange !== 'stacked'
- ? makeNest.call(this, mark, cleaned, sublevel)
- : makeNest.call(this, mark, cleaned);
- } else if (mark.summarizeX === 'count' || mark.summarizeY === 'count') {
- raw_nest = makeNest.call(this, mark, cleaned);
- }
-
- // Get the domain for the mark based on the raw data
- var raw_dom_x = mark.summarizeX === 'cumulative'
- ? [0, cleaned.length]
- : config.x.type === 'ordinal'
- ? d3
- .set(
- cleaned.map(function(m) {
- return m[config.x.column];
- })
- )
- .values()
- .filter(function(f) {
- return f;
- })
- : mark.split && mark.arrange !== 'stacked'
- ? d3.extent(
- d3.merge(
- raw_nest.nested.map(function(m) {
- return m.values.map(function(p) {
- return p.values.raw.length;
- });
- })
- )
- )
- : mark.summarizeX === 'count'
- ? d3.extent(
- raw_nest.nested.map(function(m) {
- return m.values.raw.length;
- })
- )
- : d3.extent(
- cleaned
- .map(function(m) {
- return +m[config.x.column];
- })
- .filter(function(f) {
- return +f || +f === 0;
- })
- );
-
- var raw_dom_y = mark.summarizeY === 'cumulative'
- ? [0, cleaned.length]
- : config.y.type === 'ordinal'
- ? d3
- .set(
- cleaned.map(function(m) {
- return m[config.y.column];
- })
- )
- .values()
- .filter(function(f) {
- return f;
- })
- : mark.split && mark.arrange !== 'stacked'
- ? d3.extent(
- d3.merge(
- raw_nest.nested.map(function(m) {
- return m.values.map(function(p) {
- return p.values.raw.length;
- });
- })
- )
- )
- : mark.summarizeY === 'count'
- ? d3.extent(
- raw_nest.nested.map(function(m) {
- return m.values.raw.length;
- })
- )
- : d3.extent(
- cleaned
- .map(function(m) {
- return +m[config.y.column];
- })
- .filter(function(f) {
- return +f || +f === 0;
- })
- );
-
- var filtered = cleaned;
-
- var filt1_xs = [];
- var filt1_ys = [];
- if (this.filters.length) {
- this.filters.forEach(function(e) {
- filtered = filtered.filter(function(d) {
- return e.all === true && e.index === 0
- ? d
- : e.val instanceof Array
- ? e.val.indexOf(d[e.col]) > -1
- : d[e.col] === e.val;
- });
- });
- //get domain for all non-All values of first filter
- if (config.x.behavior === 'firstfilter' || config.y.behavior === 'firstfilter') {
- this.filters[0].choices
- .filter(function(f) {
- return f !== 'All';
- })
- .forEach(function(e) {
- var perfilter = cleaned.filter(function(f) {
- return f[_this.filters[0].col] === e;
- });
- var filt_nested = makeNest.call(_this, mark, perfilter, sublevel);
- filt1_xs.push(filt_nested.dom_x);
- filt1_ys.push(filt_nested.dom_y);
- });
- }
- }
-
- //filter on mark-specific instructions
- if (mark.values) {
- var _loop = function _loop(a) {
- filtered = filtered.filter(function(f) {
- return mark.values[a].indexOf(f[a]) > -1;
- });
- };
-
- for (var a in mark.values) {
- _loop(a);
- }
- }
- var filt1_dom_x = d3.extent(d3.merge(filt1_xs));
- var filt1_dom_y = d3.extent(d3.merge(filt1_ys));
-
- var current_nested = makeNest.call(this, mark, filtered, sublevel);
-
- var flex_dom_x = current_nested.dom_x;
- var flex_dom_y = current_nested.dom_y;
-
- if (mark.type === 'bar') {
- if (config.y.type === 'ordinal' && mark.summarizeX === 'count') {
- config.x.domain = config.x.domain ? [0, config.x.domain[1]] : [0, null];
- } else if (config.x.type === 'ordinal' && mark.summarizeY === 'count') {
- config.y.domain = config.y.domain ? [0, config.y.domain[1]] : [0, null];
- }
- }
-
- //several criteria must be met in order to use the 'firstfilter' domain
- var nonall = Boolean(
- this.filters.length &&
- this.filters[0].val !== 'All' &&
- this.filters.slice(1).filter(function(f) {
- return f.val === 'All';
- }).length ===
- this.filters.length - 1
- );
-
- var pre_x_dom = !this.filters.length
- ? flex_dom_x
- : x_behavior === 'raw'
- ? raw_dom_x
- : nonall && x_behavior === 'firstfilter' ? filt1_dom_x : flex_dom_x;
- var pre_y_dom = !this.filters.length
- ? flex_dom_y
- : y_behavior === 'raw'
- ? raw_dom_y
- : nonall && y_behavior === 'firstfilter' ? filt1_dom_y : flex_dom_y;
-
- var x_dom = config.x_dom
- ? config.x_dom
- : config.x.type === 'ordinal' && config.x.behavior === 'flex'
- ? d3
- .set(
- filtered.map(function(m) {
- return m[config.x.column];
- })
- )
- .values()
- : config.x.type === 'ordinal'
- ? d3
- .set(
- cleaned.map(function(m) {
- return m[config.x.column];
- })
- )
- .values()
- : pre_x_dom;
-
- var y_dom = config.y_dom
- ? config.y_dom
- : config.y.type === 'ordinal' && config.y.behavior === 'flex'
- ? d3
- .set(
- filtered.map(function(m) {
- return m[config.y.column];
- })
- )
- .values()
- : config.y.type === 'ordinal'
- ? d3
- .set(
- cleaned.map(function(m) {
- return m[config.y.column];
- })
- )
- .values()
- : pre_y_dom;
-
- //set lower limit of linear domain to 0 when other axis is ordinal and mark type is set to 'bar', provided no values are negative
- if (mark.type === 'bar') {
- if (
- config.x.behavior !== 'flex' &&
- config.x.type === 'linear' &&
- config.y.type === 'ordinal' &&
- raw_dom_x[0] >= 0
- )
- x_dom[0] = 0;
-
- if (
- config.y.behavior !== 'flex' &&
- config.x.type === 'ordinal' &&
- config.y.type === 'linear' &&
- raw_dom_y[0] >= 0
- )
- y_dom[0] = 0;
- }
-
- //update domains with those specified in the config
- if (
- config.x.domain &&
- (config.x.domain[0] || config.x.domain[0] === 0) &&
- !isNaN(+config.x.domain[0])
- ) {
- x_dom[0] = config.x.domain[0];
- }
- if (
- config.x.domain &&
- (config.x.domain[1] || config.x.domain[1] === 0) &&
- !isNaN(+config.x.domain[1])
- ) {
- x_dom[1] = config.x.domain[1];
- }
- if (
- config.y.domain &&
- (config.y.domain[0] || config.y.domain[0] === 0) &&
- !isNaN(+config.y.domain[0])
- ) {
- y_dom[0] = config.y.domain[0];
- }
- if (
- config.y.domain &&
- (config.y.domain[1] || config.y.domain[1] === 0) &&
- !isNaN(+config.y.domain[1])
- ) {
- y_dom[1] = config.y.domain[1];
- }
-
- if (config.x.type === 'ordinal' && !config.x.order) {
- config.x.order = current_nested.totalOrder;
- }
- if (config.y.type === 'ordinal' && !config.y.order) {
- config.y.order = current_nested.totalOrder;
- }
-
- this.current_data = current_nested.nested;
-
- this.events.onDatatransform.call(this);
-
- return { config: mark, data: current_nested.nested, x_dom: x_dom, y_dom: y_dom };
- }
-
- function setColorScale() {
- var config = this.config;
- var data = config.legend.behavior === 'flex' ? this.filtered_data : this.raw_data;
- var colordom = Array.isArray(config.color_dom) && config.color_dom.length
- ? config.color_dom.slice()
- : d3
- .set(
- data.map(function(m) {
- return m[config.color_by];
- })
- )
- .values()
- .filter(function(f) {
- return f && f !== 'undefined';
- });
-
- if (config.legend.order)
- colordom.sort(function(a, b) {
- return d3.ascending(config.legend.order.indexOf(a), config.legend.order.indexOf(b));
- });
- else colordom.sort(naturalSorter);
-
- this.colorScale = d3.scale.ordinal().domain(colordom).range(config.colors);
- }
-
- function xScaleAxis(max_range, domain, type) {
- if (max_range === undefined) {
- max_range = this.plot_width;
- }
- if (domain === undefined) {
- domain = this.x_dom;
- }
- if (type === undefined) {
- type = this.config.x.type;
- }
- var config = this.config;
- var x = void 0;
-
- if (type === 'log') {
- x = d3.scale.log();
- } else if (type === 'ordinal') {
- x = d3.scale.ordinal();
- } else if (type === 'time') {
- x = d3.time.scale();
- } else {
- x = d3.scale.linear();
- }
-
- x.domain(domain);
-
- if (type === 'ordinal') {
- x.rangeBands([0, +max_range], config.padding, config.outer_pad);
- } else {
- x.range([0, +max_range]).clamp(Boolean(config.x.clamp));
- }
-
- var xFormat = config.x.format
- ? config.x.format
- : config.marks
- .map(function(m) {
- return m.summarizeX === 'percent';
- })
- .indexOf(true) > -1
- ? '0%'
- : type === 'time' ? '%x' : '.0f';
- var tick_count = Math.max(2, Math.min(max_range / 80, 8));
- var xAxis = d3.svg
- .axis()
- .scale(x)
- .orient(config.x.location)
- .ticks(tick_count)
- .tickFormat(
- type === 'ordinal'
- ? null
- : type === 'time' ? d3.time.format(xFormat) : d3.format(xFormat)
- )
- .tickValues(config.x.ticks ? config.x.ticks : null)
- .innerTickSize(6)
- .outerTickSize(3);
-
- this.svg.select('g.x.axis').attr('class', 'x axis ' + type);
- this.x = x;
- this.xAxis = xAxis;
- }
-
- function yScaleAxis(max_range, domain, type) {
- if (max_range === undefined) {
- max_range = this.plot_height;
- }
- if (domain === undefined) {
- domain = this.y_dom;
- }
- if (type === undefined) {
- type = this.config.y.type;
- }
- var config = this.config;
- var y = void 0;
- if (type === 'log') {
- y = d3.scale.log();
- } else if (type === 'ordinal') {
- y = d3.scale.ordinal();
- } else if (type === 'time') {
- y = d3.time.scale();
- } else {
- y = d3.scale.linear();
- }
-
- y.domain(domain);
-
- if (type === 'ordinal') {
- y.rangeBands([+max_range, 0], config.padding, config.outer_pad);
- } else {
- y.range([+max_range, 0]).clamp(Boolean(config.y_clamp));
- }
-
- var yFormat = config.y.format
- ? config.y.format
- : config.marks
- .map(function(m) {
- return m.summarizeY === 'percent';
- })
- .indexOf(true) > -1
- ? '0%'
- : '.0f';
- var tick_count = Math.max(2, Math.min(max_range / 80, 8));
- var yAxis = d3.svg
- .axis()
- .scale(y)
- .orient('left')
- .ticks(tick_count)
- .tickFormat(
- type === 'ordinal'
- ? null
- : type === 'time' ? d3.time.format(yFormat) : d3.format(yFormat)
- )
- .tickValues(config.y.ticks ? config.y.ticks : null)
- .innerTickSize(6)
- .outerTickSize(3);
-
- this.svg.select('g.y.axis').attr('class', 'y axis ' + type);
-
- this.y = y;
- this.yAxis = yAxis;
- }
-
- function resize() {
- var config = this.config;
-
- var aspect2 = 1 / config.aspect;
- var div_width = parseInt(this.wrap.style('width'));
- var max_width = config.max_width ? config.max_width : div_width;
- var preWidth = !config.resizable
- ? config.width
- : !max_width || div_width < max_width ? div_width : this.raw_width;
-
- this.textSize(preWidth);
-
- this.margin = this.setMargins();
-
- var svg_width = config.x.type === 'ordinal' && +config.x.range_band
- ? this.raw_width + this.margin.left + this.margin.right
- : !config.resizable
- ? this.raw_width
- : !config.max_width || div_width < config.max_width ? div_width : this.raw_width;
- this.plot_width = svg_width - this.margin.left - this.margin.right;
- var svg_height = config.y.type === 'ordinal' && +config.y.range_band
- ? this.raw_height + this.margin.top + this.margin.bottom
- : !config.resizable && config.height
- ? config.height
- : !config.resizable ? svg_width * aspect2 : this.plot_width * aspect2;
- this.plot_height = svg_height - this.margin.top - this.margin.bottom;
-
- d3
- .select(this.svg.node().parentNode)
- .attr('width', svg_width)
- .attr('height', svg_height)
- .select('g')
- .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
-
- this.svg
- .select('.overlay')
- .attr('width', this.plot_width)
- .attr('height', this.plot_height)
- .classed('zoomable', config.zoomable);
-
- this.svg
- .select('.plotting-area')
- .attr('width', this.plot_width)
- .attr('height', this.plot_height + 1)
- .attr('transform', 'translate(0, -1)');
-
- this.xScaleAxis();
- this.yScaleAxis();
-
- var g_x_axis = this.svg.select('.x.axis');
- var g_y_axis = this.svg.select('.y.axis');
- var x_axis_label = g_x_axis.select('.axis-title');
- var y_axis_label = g_y_axis.select('.axis-title');
-
- if (config.x_location !== 'top') {
- g_x_axis.attr('transform', 'translate(0,' + this.plot_height + ')');
- }
- var gXAxisTrans = config.transitions ? g_x_axis.transition() : g_x_axis;
- gXAxisTrans.call(this.xAxis);
- var gYAxisTrans = config.transitions ? g_y_axis.transition() : g_y_axis;
- gYAxisTrans.call(this.yAxis);
-
- x_axis_label.attr(
- 'transform',
- 'translate(' + this.plot_width / 2 + ',' + (this.margin.bottom - 2) + ')'
- );
- y_axis_label.attr('x', -1 * this.plot_height / 2).attr('y', -1 * this.margin.left);
-
- this.svg
- .selectAll('.axis .domain')
- .attr({
- fill: 'none',
- stroke: '#ccc',
- 'stroke-width': 1,
- 'shape-rendering': 'crispEdges'
- });
- this.svg
- .selectAll('.axis .tick line')
- .attr({ stroke: '#eee', 'stroke-width': 1, 'shape-rendering': 'crispEdges' });
-
- this.drawGridlines();
-
- //update legend - margins need to be set first
- this.makeLegend();
-
- //update the chart's specific marks
- this.updateDataMarks();
-
- //call .on("resize") function, if any
- this.events.onResize.call(this);
- }
-
- function textSize(width) {
- var font_size = '14px';
- var point_size = 4;
- var stroke_width = 2;
-
- if (!this.config.scale_text) {
- font_size = this.config.font_size;
- point_size = this.config.point_size || 4;
- stroke_width = this.config.stroke_width || 2;
- } else if (width >= 600) {
- font_size = '14px';
- point_size = 4;
- stroke_width = 2;
- } else if (width > 450 && width < 600) {
- font_size = '12px';
- point_size = 3;
- stroke_width = 2;
- } else if (width > 300 && width < 450) {
- font_size = '10px';
- point_size = 2;
- stroke_width = 2;
- } else if (width <= 300) {
- font_size = '10px';
- point_size = 2;
- stroke_width = 1;
- }
-
- this.wrap.style('font-size', font_size);
- this.config.flex_point_size = point_size;
- this.config.flex_stroke_width = stroke_width;
- }
-
- function setMargins() {
- var _this = this;
-
- var y_ticks = this.yAxis.tickFormat()
- ? this.y.domain().map(function(m) {
- return _this.yAxis.tickFormat()(m);
- })
- : this.y.domain();
-
- var max_y_text_length = d3.max(
- y_ticks.map(function(m) {
- return String(m).length;
- })
- );
- if (this.config.y_format && this.config.y_format.indexOf('%') > -1) {
- max_y_text_length += 1;
- }
- max_y_text_length = Math.max(2, max_y_text_length);
- var x_label_on = this.config.x.label ? 1.5 : 0;
- var y_label_on = this.config.y.label ? 1.5 : 0.25;
- var font_size = parseInt(this.wrap.style('font-size'));
- var x_second = this.config.x2_interval ? 1 : 0;
- var y_margin = max_y_text_length * font_size * 0.5 + font_size * y_label_on * 1.5 || 8;
- var x_margin =
- font_size + font_size / 1.5 + font_size * x_label_on + font_size * x_second || 8;
-
- y_margin += 6;
- x_margin += 3;
-
- return {
- top: this.config.margin && this.config.margin.top ? this.config.margin.top : 8,
- right: this.config.margin && this.config.margin.right ? this.config.margin.right : 16,
- bottom: this.config.margin && this.config.margin.bottom
- ? this.config.margin.bottom
- : x_margin,
- left: this.config.margin && this.config.margin.left ? this.config.margin.left : y_margin
- };
- }
-
- function drawGridLines() {
- this.wrap.classed('gridlines', this.config.gridlines);
- if (this.config.gridlines) {
- this.svg.select('.y.axis').selectAll('.tick line').attr('x1', 0);
- this.svg.select('.x.axis').selectAll('.tick line').attr('y1', 0);
- if (this.config.gridlines === 'y' || this.config.gridlines === 'xy')
- this.svg.select('.y.axis').selectAll('.tick line').attr('x1', this.plot_width);
- if (this.config.gridlines === 'x' || this.config.gridlines === 'xy')
- this.svg.select('.x.axis').selectAll('.tick line').attr('y1', -this.plot_height);
- } else {
- this.svg.select('.y.axis').selectAll('.tick line').attr('x1', 0);
- this.svg.select('.x.axis').selectAll('.tick line').attr('y1', 0);
- }
- }
-
- function makeLegend() {
- var scale = arguments.length > 0 && arguments[0] !== undefined
- ? arguments[0]
- : this.colorScale;
- var label = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
- var custom_data = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
-
- var config = this.config;
-
- config.legend.mark = config.legend.mark
- ? config.legend.mark
- : config.marks.length && config.marks[0].type === 'bar'
- ? 'square'
- : config.marks.length ? config.marks[0].type : 'square';
-
- var legend_label = label
- ? label
- : typeof config.legend.label === 'string' ? config.legend.label : '';
-
- var legendOriginal = this.legend || this.wrap.select('.legend');
- var legend = legendOriginal;
-
- if (!this.parent) {
- //singular chart
- if (this.config.legend.location === 'top' || this.config.legend.location === 'left') {
- this.wrap.node().insertBefore(legendOriginal.node(), this.svg.node().parentNode);
- } else {
- this.wrap.node().appendChild(legendOriginal.node());
- }
- } else {
- //multiples - keep legend outside of individual charts' wraps
- if (this.config.legend.location === 'top' || this.config.legend.location === 'left') {
- this.parent.wrap
- .node()
- .insertBefore(
- legendOriginal.node(),
- this.parent.wrap.select('.wc-chart').node()
- );
- } else {
- this.parent.wrap.node().appendChild(legendOriginal.node());
- }
- }
-
- legend.style('padding', 0);
-
- var legend_data =
- custom_data ||
- scale
- .domain()
- .slice(0)
- .filter(function(f) {
- return f !== undefined && f !== null;
- })
- .map(function(m) {
- return { label: m, mark: config.legend.mark };
- });
-
- legend
- .select('.legend-title')
- .text(legend_label)
- .style('display', legend_label ? 'inline' : 'none')
- .style('margin-right', '1em');
-
- var leg_parts = legend.selectAll('.legend-item').data(legend_data, function(d) {
- return d.label + d.mark;
- });
-
- leg_parts.exit().remove();
-
- var legendPartDisplay = this.config.legend.location === 'bottom' ||
- this.config.legend.location === 'top'
- ? 'inline-block'
- : 'block';
- var new_parts = leg_parts
- .enter()
- .append('li')
- .attr('class', 'legend-item')
- .style({ 'list-style-type': 'none', 'margin-right': '1em' });
- new_parts.append('span').attr('class', 'legend-mark-text').style('color', function(d) {
- return scale(d.label);
- });
- new_parts
- .append('svg')
- .attr('class', 'legend-color-block')
- .attr('width', '1.1em')
- .attr('height', '1.1em')
- .style({
- position: 'relative',
- top: '0.2em'
- });
-
- leg_parts.style('display', legendPartDisplay);
-
- if (config.legend.order) {
- leg_parts.sort(function(a, b) {
- return d3.ascending(
- config.legend.order.indexOf(a.label),
- config.legend.order.indexOf(b.label)
- );
- });
- }
-
- leg_parts.selectAll('.legend-color-block').select('.legend-mark').remove();
- leg_parts.selectAll('.legend-color-block').each(function(e) {
- var svg = d3.select(this);
- if (e.mark === 'circle') {
- svg.append('circle').attr({
- cx: '.5em',
- cy: '.5em',
- r: '.45em',
- class: 'legend-mark'
- });
- } else if (e.mark === 'line') {
- svg.append('line').attr({
- x1: 0,
- y1: '.5em',
- x2: '1em',
- y2: '.5em',
- 'stroke-width': 2,
- 'shape-rendering': 'crispEdges',
- class: 'legend-mark'
- });
- } else if (e.mark === 'square') {
- svg.append('rect').attr({
- height: '1em',
- width: '1em',
- class: 'legend-mark',
- 'shape-rendering': 'crispEdges'
- });
- }
- });
- leg_parts
- .selectAll('.legend-color-block')
- .select('.legend-mark')
- .attr('fill', function(d) {
- return d.color || scale(d.label);
- })
- .attr('stroke', function(d) {
- return d.color || scale(d.label);
- })
- .each(function(e) {
- d3.select(this).attr(e.attributes);
- });
-
- new_parts
- .append('span')
- .attr('class', 'legend-label')
- .style('margin-left', '0.25em')
- .text(function(d) {
- return d.label;
- });
-
- if (scale.domain().length > 0) {
- var legendDisplay = (this.config.legend.location === 'bottom' ||
- this.config.legend.location === 'top') &&
- !this.parent
- ? 'block'
- : 'inline-block';
- legend.style('display', legendDisplay);
- } else {
- legend.style('display', 'none');
- }
-
- this.legend = legend;
- }
-
- function updateDataMarks() {
- this.drawBars(
- this.marks.filter(function(f) {
- return f.type === 'bar';
- })
- );
- this.drawLines(
- this.marks.filter(function(f) {
- return f.type === 'line';
- })
- );
- this.drawPoints(
- this.marks.filter(function(f) {
- return f.type === 'circle';
- })
- );
- this.drawText(
- this.marks.filter(function(f) {
- return f.type === 'text';
- })
- );
-
- this.marks.supergroups = this.svg.selectAll('g.supergroup');
- }
-
- function drawArea(area_drawer, area_data, datum_accessor) {
- var class_match = arguments.length > 3 && arguments[3] !== undefined
- ? arguments[3]
- : 'chart-area';
-
- var _this = this;
-
- var bind_accessor = arguments[4];
- var attr_accessor = arguments.length > 5 && arguments[5] !== undefined
- ? arguments[5]
- : function(d) {
- return d;
- };
-
- var area_grps = this.svg.selectAll('.' + class_match).data(area_data, bind_accessor);
- area_grps.exit().remove();
- area_grps
- .enter()
- .append('g')
- .attr('class', function(d) {
- return class_match + ' ' + d.key;
- })
- .append('path');
-
- var areaPaths = area_grps
- .select('path')
- .datum(datum_accessor)
- .attr('fill', function(d) {
- var d_attr = attr_accessor(d);
- return d_attr ? _this.colorScale(d_attr[_this.config.color_by]) : null;
- })
- .attr(
- 'fill-opacity',
- this.config.fill_opacity || this.config.fill_opacity === 0
- ? this.config.fill_opacity
- : 0.3
- );
-
- //don't transition if config says not to
- var areaPathTransitions = this.config.transitions ? areaPaths.transition() : areaPaths;
-
- areaPathTransitions.attr('d', area_drawer);
-
- return area_grps;
- }
-
- function drawBars(marks) {
- var _this = this;
-
- var chart = this;
- var rawData = this.raw_data;
- var config = this.config;
-
- var bar_supergroups = this.svg.selectAll('.bar-supergroup').data(marks, function(d, i) {
- return i + '-' + d.per.join('-');
- });
-
- bar_supergroups.enter().append('g').attr('class', function(d) {
- return 'supergroup bar-supergroup ' + d.id;
- });
-
- bar_supergroups.exit().remove();
-
- var bar_groups = bar_supergroups.selectAll('.bar-group').data(
- function(d) {
- return d.data;
- },
- function(d) {
- return d.key;
- }
- );
- var old_bar_groups = bar_groups.exit();
-
- var nu_bar_groups = void 0;
- var bars = void 0;
-
- var oldBarsTrans = config.transitions
- ? old_bar_groups.selectAll('.bar').transition()
- : old_bar_groups.selectAll('.bar');
- var oldBarGroupsTrans = config.transitions ? old_bar_groups.transition() : old_bar_groups;
-
- if (config.x.type === 'ordinal') {
- oldBarsTrans.attr('y', this.y(0)).attr('height', 0);
-
- oldBarGroupsTrans.remove();
-
- nu_bar_groups = bar_groups.enter().append('g').attr('class', function(d) {
- return 'bar-group ' + d.key;
- });
- nu_bar_groups.append('title');
-
- bars = bar_groups.selectAll('rect').data(
- function(d) {
- return d.values instanceof Array
- ? d.values.sort(function(a, b) {
- return (
- _this.colorScale.domain().indexOf(b.key) -
- _this.colorScale.domain().indexOf(a.key)
- );
- })
- : [d];
- },
- function(d) {
- return d.key;
- }
- );
-
- var exitBars = config.transitions ? bars.exit().transition() : bars.exit();
- exitBars.attr('y', this.y(0)).attr('height', 0).remove();
- bars
- .enter()
- .append('rect')
- .attr('class', function(d) {
- return 'wc-data-mark bar ' + d.key;
- })
- .style('clip-path', 'url(#' + chart.id + ')')
- .attr('y', this.y(0))
- .attr('height', 0)
- .append('title');
-
- bars
- .attr('shape-rendering', 'crispEdges')
- .attr('stroke', function(d) {
- return _this.colorScale(d.values.raw[0][config.color_by]);
- })
- .attr('fill', function(d) {
- return _this.colorScale(d.values.raw[0][config.color_by]);
- });
-
- bars.each(function(d) {
- var mark = d3.select(this.parentNode.parentNode).datum();
- d.tooltip = mark.tooltip;
- d.arrange = mark.split && mark.arrange
- ? mark.arrange
- : mark.split ? 'grouped' : null;
- d.subcats = config.legend.order
- ? config.legend.order.slice().reverse()
- : mark.values && mark.values[mark.split]
- ? mark.values[mark.split]
- : d3
- .set(
- rawData.map(function(m) {
- return m[mark.split];
- })
- )
- .values();
- d3.select(this).attr(mark.attributes);
- });
-
- var xformat = config.marks
- .map(function(m) {
- return m.summarizeX === 'percent';
- })
- .indexOf(true) > -1
- ? d3.format('0%')
- : d3.format(config.x.format);
- var yformat = config.marks
- .map(function(m) {
- return m.summarizeY === 'percent';
- })
- .indexOf(true) > -1
- ? d3.format('0%')
- : d3.format(config.y.format);
- bars.select('title').text(function(d) {
- var tt = d.tooltip || '';
- return tt
- .replace(/\$x/g, xformat(d.values.x))
- .replace(/\$y/g, yformat(d.values.y))
- .replace(/\[(.+?)\]/g, function(str, orig) {
- return d.values.raw[0][orig];
- });
- });
-
- var barsTrans = config.transitions ? bars.transition() : bars;
- barsTrans
- .attr('x', function(d) {
- var position = void 0;
- if (!d.arrange || d.arrange === 'stacked') {
- return _this.x(d.values.x);
- } else if (d.arrange === 'nested') {
- var _position = d.subcats.indexOf(d.key);
- var offset = _position
- ? _this.x.rangeBand() / (d.subcats.length * 0.75) / _position
- : _this.x.rangeBand();
- return _this.x(d.values.x) + (_this.x.rangeBand() - offset) / 2;
- } else {
- position = d.subcats.indexOf(d.key);
- return (
- _this.x(d.values.x) + _this.x.rangeBand() / d.subcats.length * position
- );
- }
- })
- .attr('y', function(d) {
- if (d.arrange !== 'stacked') {
- return _this.y(d.values.y);
- } else {
- return _this.y(d.values.start);
- }
- })
- .attr('width', function(d) {
- if (!d.arrange || d.arrange === 'stacked') {
- return _this.x.rangeBand();
- } else if (d.arrange === 'nested') {
- var position = d.subcats.indexOf(d.key);
- return position
- ? _this.x.rangeBand() / (d.subcats.length * 0.75) / position
- : _this.x.rangeBand();
- } else {
- return _this.x.rangeBand() / d.subcats.length;
- }
- })
- .attr('height', function(d) {
- return _this.y(0) - _this.y(d.values.y);
- });
- } else if (config.y.type === 'ordinal') {
- oldBarsTrans.attr('x', this.x(0)).attr('width', 0);
-
- oldBarGroupsTrans.remove();
-
- nu_bar_groups = bar_groups.enter().append('g').attr('class', function(d) {
- return 'bar-group ' + d.key;
- });
- nu_bar_groups.append('title');
-
- bars = bar_groups.selectAll('rect').data(
- function(d) {
- return d.values instanceof Array
- ? d.values.sort(function(a, b) {
- return (
- _this.colorScale.domain().indexOf(b.key) -
- _this.colorScale.domain().indexOf(a.key)
- );
- })
- : [d];
- },
- function(d) {
- return d.key;
- }
- );
-
- var _exitBars = config.transitions ? bars.exit().transition() : bars.exit();
- _exitBars.attr('x', this.x(0)).attr('width', 0).remove();
- bars
- .enter()
- .append('rect')
- .attr('class', function(d) {
- return 'wc-data-mark bar ' + d.key;
- })
- .style('clip-path', 'url(#' + chart.id + ')')
- .attr('x', this.x(0))
- .attr('width', 0)
- .append('title');
-
- bars
- .attr('shape-rendering', 'crispEdges')
- .attr('stroke', function(d) {
- return _this.colorScale(d.values.raw[0][config.color_by]);
- })
- .attr('fill', function(d) {
- return _this.colorScale(d.values.raw[0][config.color_by]);
- });
-
- bars.each(function(d) {
- var mark = d3.select(this.parentNode.parentNode).datum();
- d.arrange = mark.split && mark.arrange
- ? mark.arrange
- : mark.split ? 'grouped' : null;
- d.subcats = config.legend.order
- ? config.legend.order.slice().reverse()
- : mark.values && mark.values[mark.split]
- ? mark.values[mark.split]
- : d3
- .set(
- rawData.map(function(m) {
- return m[mark.split];
- })
- )
- .values();
- d.tooltip = mark.tooltip;
- d3.select(this).attr(mark.attributes);
- });
-
- var _xformat = config.marks
- .map(function(m) {
- return m.summarizeX === 'percent';
- })
- .indexOf(true) > -1
- ? d3.format('0%')
- : d3.format(config.x.format);
- var _yformat = config.marks
- .map(function(m) {
- return m.summarizeY === 'percent';
- })
- .indexOf(true) > -1
- ? d3.format('0%')
- : d3.format(config.y.format);
- bars.select('title').text(function(d) {
- var tt = d.tooltip || '';
- return tt
- .replace(/\$x/g, _xformat(d.values.x))
- .replace(/\$y/g, _yformat(d.values.y))
- .replace(/\[(.+?)\]/g, function(str, orig) {
- return d.values.raw[0][orig];
- });
- });
-
- var _barsTrans = config.transitions ? bars.transition() : bars;
- _barsTrans
- .attr('x', function(d) {
- if (d.arrange === 'stacked' || !d.arrange) {
- return d.values.start !== undefined ? _this.x(d.values.start) : _this.x(0);
- } else {
- return _this.x(0);
- }
- })
- .attr('y', function(d) {
- if (d.arrange === 'nested') {
- var position = d.subcats.indexOf(d.key);
- var offset = position
- ? _this.y.rangeBand() / (d.subcats.length * 0.75) / position
- : _this.y.rangeBand();
- return _this.y(d.values.y) + (_this.y.rangeBand() - offset) / 2;
- } else if (d.arrange === 'grouped') {
- var _position2 = d.subcats.indexOf(d.key);
- return (
- _this.y(d.values.y) +
- _this.y.rangeBand() / d.subcats.length * _position2
- );
- } else {
- return _this.y(d.values.y);
- }
- })
- .attr('width', function(d) {
- return _this.x(d.values.x) - _this.x(0);
- })
- .attr('height', function(d) {
- if (config.y.type === 'quantile') {
- return 20;
- } else if (d.arrange === 'nested') {
- var position = d.subcats.indexOf(d.key);
- return position
- ? _this.y.rangeBand() / (d.subcats.length * 0.75) / position
- : _this.y.rangeBand();
- } else if (d.arrange === 'grouped') {
- return _this.y.rangeBand() / d.subcats.length;
- } else {
- return _this.y.rangeBand();
- }
- });
- } else if (['linear', 'log'].indexOf(config.x.type) > -1 && config.x.bin) {
- oldBarsTrans.attr('y', this.y(0)).attr('height', 0);
-
- oldBarGroupsTrans.remove();
-
- nu_bar_groups = bar_groups.enter().append('g').attr('class', function(d) {
- return 'bar-group ' + d.key;
- });
- nu_bar_groups.append('title');
-
- bars = bar_groups.selectAll('rect').data(
- function(d) {
- return d.values instanceof Array ? d.values : [d];
- },
- function(d) {
- return d.key;
- }
- );
-
- var _exitBars2 = config.transitions ? bars.exit().transition() : bars.exit();
- _exitBars2.attr('y', this.y(0)).attr('height', 0).remove();
- bars
- .enter()
- .append('rect')
- .attr('class', function(d) {
- return 'wc-data-mark bar ' + d.key;
- })
- .style('clip-path', 'url(#' + chart.id + ')')
- .attr('y', this.y(0))
- .attr('height', 0)
- .append('title');
-
- bars
- .attr('shape-rendering', 'crispEdges')
- .attr('stroke', function(d) {
- return _this.colorScale(d.values.raw[0][config.color_by]);
- })
- .attr('fill', function(d) {
- return _this.colorScale(d.values.raw[0][config.color_by]);
- });
-
- bars.each(function(d) {
- var mark = d3.select(this.parentNode.parentNode).datum();
- d.arrange = mark.split ? mark.arrange : null;
- d.subcats = config.legend.order
- ? config.legend.order.slice().reverse()
- : mark.values && mark.values[mark.split]
- ? mark.values[mark.split]
- : d3
- .set(
- rawData.map(function(m) {
- return m[mark.split];
- })
- )
- .values();
- d3.select(this).attr(mark.attributes);
- var parent = d3.select(this.parentNode).datum();
- var rangeSet = parent.key.split(',').map(function(m) {
- return +m;
- });
- d.rangeLow = d3.min(rangeSet);
- d.rangeHigh = d3.max(rangeSet);
- d.tooltip = mark.tooltip;
- });
-
- var _xformat2 = config.marks
- .map(function(m) {
- return m.summarizeX === 'percent';
- })
- .indexOf(true) > -1
- ? d3.format('0%')
- : d3.format(config.x.format);
- var _yformat2 = config.marks
- .map(function(m) {
- return m.summarizeY === 'percent';
- })
- .indexOf(true) > -1
- ? d3.format('0%')
- : d3.format(config.y.format);
- bars.select('title').text(function(d) {
- var tt = d.tooltip || '';
- return tt
- .replace(/\$x/g, _xformat2(d.values.x))
- .replace(/\$y/g, _yformat2(d.values.y))
- .replace(/\[(.+?)\]/g, function(str, orig) {
- return d.values.raw[0][orig];
- });
- });
-
- var _barsTrans2 = config.transitions ? bars.transition() : bars;
- _barsTrans2
- .attr('x', function(d) {
- return _this.x(d.rangeLow);
- })
- .attr('y', function(d) {
- if (d.arrange !== 'stacked') {
- return _this.y(d.values.y);
- } else {
- return _this.y(d.values.start);
- }
- })
- .attr('width', function(d) {
- return _this.x(d.rangeHigh) - _this.x(d.rangeLow);
- })
- .attr('height', function(d) {
- return _this.y(0) - _this.y(d.values.y);
- });
- } else if (
- ['linear', 'log'].indexOf(config.y.type) > -1 &&
- config.y.type === 'linear' &&
- config.y.bin
- ) {
- oldBarsTrans.attr('x', this.x(0)).attr('width', 0);
- oldBarGroupsTrans.remove();
-
- nu_bar_groups = bar_groups.enter().append('g').attr('class', function(d) {
- return 'bar-group ' + d.key;
- });
- nu_bar_groups.append('title');
-
- bars = bar_groups.selectAll('rect').data(
- function(d) {
- return d.values instanceof Array ? d.values : [d];
- },
- function(d) {
- return d.key;
- }
- );
-
- var _exitBars3 = config.transitions ? bars.exit().transition() : bars.exit();
- _exitBars3.attr('x', this.x(0)).attr('width', 0).remove();
- bars
- .enter()
- .append('rect')
- .attr('class', function(d) {
- return 'wc-data-mark bar ' + d.key;
- })
- .style('clip-path', 'url(#' + chart.id + ')')
- .attr('x', this.x(0))
- .attr('width', 0)
- .append('title');
-
- bars
- .attr('shape-rendering', 'crispEdges')
- .attr('stroke', function(d) {
- return _this.colorScale(d.values.raw[0][config.color_by]);
- })
- .attr('fill', function(d) {
- return _this.colorScale(d.values.raw[0][config.color_by]);
- });
-
- bars.each(function(d) {
- var mark = d3.select(this.parentNode.parentNode).datum();
- d.arrange = mark.split ? mark.arrange : null;
- d.subcats = config.legend.order
- ? config.legend.order.slice().reverse()
- : mark.values && mark.values[mark.split]
- ? mark.values[mark.split]
- : d3
- .set(
- rawData.map(function(m) {
- return m[mark.split];
- })
- )
- .values();
- var parent = d3.select(this.parentNode).datum();
- var rangeSet = parent.key.split(',').map(function(m) {
- return +m;
- });
- d.rangeLow = d3.min(rangeSet);
- d.rangeHigh = d3.max(rangeSet);
- d.tooltip = mark.tooltip;
- });
-
- var _xformat3 = config.marks
- .map(function(m) {
- return m.summarizeX === 'percent';
- })
- .indexOf(true) > -1
- ? d3.format('0%')
- : d3.format(config.x.format);
- var _yformat3 = config.marks
- .map(function(m) {
- return m.summarizeY === 'percent';
- })
- .indexOf(true) > -1
- ? d3.format('0%')
- : d3.format(config.y.format);
- bars.select('title').text(function(d) {
- var tt = d.tooltip || '';
- return tt
- .replace(/\$x/g, _xformat3(d.values.x))
- .replace(/\$y/g, _yformat3(d.values.y))
- .replace(/\[(.+?)\]/g, function(str, orig) {
- return d.values.raw[0][orig];
- });
- });
-
- var _barsTrans3 = config.transitions ? bars.transition() : bars;
- _barsTrans3
- .attr('x', function(d) {
- if (d.arrange === 'stacked') {
- return _this.x(d.values.start);
- } else {
- return _this.x(0);
- }
- })
- .attr('y', function(d) {
- return _this.y(d.rangeHigh);
- })
- .attr('width', function(d) {
- return _this.x(d.values.x);
- })
- .attr('height', function(d) {
- return _this.y(d.rangeLow) - _this.y(d.rangeHigh);
- });
- } else {
- oldBarsTrans.attr('y', this.y(0)).attr('height', 0);
- oldBarGroupsTrans.remove();
- bar_supergroups.remove();
- }
-
- //Link to the d3.selection from the data
- bar_supergroups.each(function(d) {
- d.supergroup = d3.select(this);
- d.groups = d.supergroup.selectAll('.bar-group');
- });
- }
-
- function drawLines(marks) {
- var _this = this;
-
- var chart = this;
- var config = this.config;
- var line = d3.svg
- .line()
- .interpolate(config.interpolate)
- .x(function(d) {
- return config.x.type === 'linear' || config.x.type == 'log'
- ? _this.x(+d.values.x)
- : config.x.type === 'time'
- ? _this.x(new Date(d.values.x))
- : _this.x(d.values.x) + _this.x.rangeBand() / 2;
- })
- .y(function(d) {
- return config.y.type === 'linear' || config.y.type == 'log'
- ? _this.y(+d.values.y)
- : config.y.type === 'time'
- ? _this.y(new Date(d.values.y))
- : _this.y(d.values.y) + _this.y.rangeBand() / 2;
- });
-
- var line_supergroups = this.svg.selectAll('.line-supergroup').data(marks, function(d, i) {
- return i + '-' + d.per.join('-');
- });
-
- line_supergroups.enter().append('g').attr('class', function(d) {
- return 'supergroup line-supergroup ' + d.id;
- });
-
- line_supergroups.exit().remove();
-
- var line_grps = line_supergroups.selectAll('.line').data(
- function(d) {
- return d.data;
- },
- function(d) {
- return d.key;
- }
- );
- line_grps.exit().remove();
- var nu_line_grps = line_grps.enter().append('g').attr('class', function(d) {
- return d.key + ' line';
- });
- nu_line_grps.append('path');
- nu_line_grps.append('title');
-
- var linePaths = line_grps
- .select('path')
- .attr('class', 'wc-data-mark')
- .style('clip-path', 'url(#' + chart.id + ')')
- .datum(function(d) {
- return d.values;
- })
- .attr('stroke', function(d) {
- return _this.colorScale(d[0].values.raw[0][config.color_by]);
- })
- .attr(
- 'stroke-width',
- config.stroke_width ? config.stroke_width : config.flex_stroke_width
- )
- .attr('stroke-linecap', 'round')
- .attr('fill', 'none');
- var linePathsTrans = config.transitions ? linePaths.transition() : linePaths;
- linePathsTrans.attr('d', line);
-
- line_grps.each(function(d) {
- var mark = d3.select(this.parentNode).datum();
- d.tooltip = mark.tooltip;
- d3.select(this).select('path').attr(mark.attributes);
- });
-
- line_grps.select('title').text(function(d) {
- var tt = d.tooltip || '';
- var xformat = config.x.summary === 'percent'
- ? d3.format('0%')
- : d3.format(config.x.format);
- var yformat = config.y.summary === 'percent'
- ? d3.format('0%')
- : d3.format(config.y.format);
- return tt
- .replace(/\$x/g, xformat(d.values.x))
- .replace(/\$y/g, yformat(d.values.y))
- .replace(/\[(.+?)\]/g, function(str, orig) {
- return d.values[0].values.raw[0][orig];
- });
- });
-
- //Link to the d3.selection from the data
- line_supergroups.each(function(d) {
- d.supergroup = d3.select(this);
- d.groups = d.supergroup.selectAll('g.line');
- d.paths = d.groups.select('path');
- });
- return line_grps;
- }
-
- function drawPoints(marks) {
- var _this = this;
-
- var chart = this;
- var config = this.config;
-
- var point_supergroups = this.svg.selectAll('.point-supergroup').data(marks, function(d, i) {
- return i + '-' + d.per.join('-');
- });
-
- point_supergroups.enter().append('g').attr('class', function(d) {
- return 'supergroup point-supergroup ' + d.id;
- });
-
- point_supergroups.exit().remove();
-
- var points = point_supergroups.selectAll('.point').data(
- function(d) {
- return d.data;
- },
- function(d) {
- return d.key;
- }
- );
- var oldPoints = points.exit();
-
- var oldPointsTrans = config.transitions
- ? oldPoints.selectAll('circle').transition()
- : oldPoints.selectAll('circle');
- oldPointsTrans.attr('r', 0);
-
- var oldPointGroupTrans = config.transitions ? oldPoints.transition() : oldPoints;
- oldPointGroupTrans.remove();
-
- var nupoints = points.enter().append('g').attr('class', function(d) {
- return d.key + ' point';
- });
- nupoints.append('circle').attr('class', 'wc-data-mark').attr('r', 0);
- nupoints.append('title');
- //static attributes
- points
- .select('circle')
- .style('clip-path', 'url(#' + chart.id + ')')
- .attr(
- 'fill-opacity',
- config.fill_opacity || config.fill_opacity === 0 ? config.fill_opacity : 0.6
- )
- .attr('fill', function(d) {
- return _this.colorScale(d.values.raw[0][config.color_by]);
- })
- .attr('stroke', function(d) {
- return _this.colorScale(d.values.raw[0][config.color_by]);
- });
- //attach mark info
- points.each(function(d) {
- var mark = d3.select(this.parentNode).datum();
- d.mark = mark;
- d3.select(this).select('circle').attr(mark.attributes);
- });
- //animated attributes
- var pointsTrans = config.transitions
- ? points.select('circle').transition()
- : points.select('circle');
- pointsTrans
- .attr('r', function(d) {
- return d.mark.radius || config.flex_point_size;
- })
- .attr('cx', function(d) {
- var x_pos = _this.x(d.values.x) || 0;
- return config.x.type === 'ordinal' ? x_pos + _this.x.rangeBand() / 2 : x_pos;
- })
- .attr('cy', function(d) {
- var y_pos = _this.y(d.values.y) || 0;
- return config.y.type === 'ordinal' ? y_pos + _this.y.rangeBand() / 2 : y_pos;
- });
-
- points.select('title').text(function(d) {
- var tt = d.mark.tooltip || '';
- var xformat = config.x.summary === 'percent'
- ? d3.format('0%')
- : config.x.type === 'time'
- ? d3.time.format(config.x.format)
- : d3.format(config.x.format);
- var yformat = config.y.summary === 'percent'
- ? d3.format('0%')
- : config.y.type === 'time'
- ? d3.time.format(config.y.format)
- : d3.format(config.y.format);
- return tt
- .replace(
- /\$x/g,
- config.x.type === 'time' ? xformat(new Date(d.values.x)) : xformat(d.values.x)
- )
- .replace(
- /\$y/g,
- config.y.type === 'time' ? yformat(new Date(d.values.y)) : yformat(d.values.y)
- )
- .replace(/\[(.+?)\]/g, function(str, orig) {
- return d.values.raw[0][orig];
- });
- });
-
- //Link to the d3.selection from the data
- point_supergroups.each(function(d) {
- d.supergroup = d3.select(this);
- d.groups = d.supergroup.selectAll('g.point');
- d.circles = d.groups.select('circle');
- });
-
- return points;
- }
-
- function drawText(marks) {
- var _this = this;
-
- var chart = this;
- var config = this.config;
-
- var textSupergroups = this.svg.selectAll('.text-supergroup').data(marks, function(d, i) {
- return i + '-' + d.per.join('-');
- });
-
- textSupergroups.enter().append('g').attr('class', function(d) {
- return 'supergroup text-supergroup ' + d.id;
- });
-
- textSupergroups.exit().remove();
-
- var texts = textSupergroups.selectAll('.text').data(
- function(d) {
- return d.data;
- },
- function(d) {
- return d.key;
- }
- );
- var oldTexts = texts.exit();
-
- // don't need to transition position of outgoing text
- // const oldTextsTrans = config.transitions ? oldTexts.selectAll('text').transition() : oldTexts.selectAll('text');
-
- var oldTextGroupTrans = config.transitions ? oldTexts.transition() : oldTexts;
- oldTextGroupTrans.remove();
-
- var nutexts = texts.enter().append('g').attr('class', function(d) {
- return d.key + ' text';
- });
- nutexts.append('text').attr('class', 'wc-data-mark');
- // don't need to set initial location for incoming text
-
- // attach mark info
- function attachMarks(d) {
- d.mark = d3.select(this.parentNode).datum();
- d3.select(this).select('text').attr(d.mark.attributes);
- }
- texts.each(attachMarks);
-
- // parse text like tooltips
- texts.select('text').style('clip-path', 'url(#' + chart.id + ')').text(function(d) {
- var tt = d.mark.text || '';
- var xformat = config.x.summary === 'percent'
- ? d3.format('0%')
- : config.x.type === 'time'
- ? d3.time.format(config.x.format)
- : d3.format(config.x.format);
- var yformat = config.y.summary === 'percent'
- ? d3.format('0%')
- : config.y.type === 'time'
- ? d3.time.format(config.y.format)
- : d3.format(config.y.format);
- return tt
- .replace(
- /\$x/g,
- config.x.type === 'time' ? xformat(new Date(d.values.x)) : xformat(d.values.x)
- )
- .replace(
- /\$y/g,
- config.y.type === 'time' ? yformat(new Date(d.values.y)) : yformat(d.values.y)
- )
- .replace(/\[(.+?)\]/g, function(str, orig) {
- return d.values.raw[0][orig];
- });
- });
- // animated attributes
- var textsTrans = config.transitions
- ? texts.select('text').transition()
- : texts.select('text');
- textsTrans
- .attr('x', function(d) {
- var xPos = _this.x(d.values.x) || 0;
- return config.x.type === 'ordinal' ? xPos + _this.x.rangeBand() / 2 : xPos;
- })
- .attr('y', function(d) {
- var yPos = _this.y(d.values.y) || 0;
- return config.y.type === 'ordinal' ? yPos + _this.y.rangeBand() / 2 : yPos;
- });
- //add a reference to the selection from it's data
- textSupergroups.each(function(d) {
- d.supergroup = d3.select(this);
- d.groups = d.supergroup.selectAll('g.text');
- d.texts = d.groups.select('text');
- });
- return texts;
- }
-
- function destroy() {
- var destroyControls = arguments.length > 0 && arguments[0] !== undefined
- ? arguments[0]
- : true;
-
- //run onDestroy callback
- this.events.onDestroy.call(this);
-
- //remove resize event listener
- var context = this;
- d3.select(window).on('resize.' + context.element + context.id, null);
-
- //destroy controls
- if (destroyControls && this.controls) {
- this.controls.destroy();
- }
-
- //unmount chart wrapper
- this.wrap.remove();
- }
-
- var chartProto = {
- raw_data: [],
- config: {}
- };
-
- var chart = Object.create(chartProto, {
- checkRequired: { value: checkRequired },
- consolidateData: { value: consolidateData },
- draw: { value: draw },
- destroy: { value: destroy },
- drawArea: { value: drawArea },
- drawBars: { value: drawBars },
- drawGridlines: { value: drawGridLines },
- drawLines: { value: drawLines },
- drawPoints: { value: drawPoints },
- drawText: { value: drawText },
- init: { value: init },
- layout: { value: layout },
- makeLegend: { value: makeLegend },
- resize: { value: resize },
- setColorScale: { value: setColorScale },
- setDefaults: { value: setDefaults },
- setMargins: { value: setMargins },
- textSize: { value: textSize },
- transformData: { value: transformData },
- updateDataMarks: { value: updateDataMarks },
- xScaleAxis: { value: xScaleAxis },
- yScaleAxis: { value: yScaleAxis }
- });
-
- var chartCount = 0;
-
- function createChart() {
- var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'body';
- var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
- var controls = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
-
- var thisChart = Object.create(chart);
-
- thisChart.div = element;
-
- thisChart.config = Object.create(config);
-
- thisChart.controls = controls;
-
- thisChart.raw_data = [];
-
- thisChart.filters = [];
-
- thisChart.marks = [];
-
- thisChart.wrap = d3.select(thisChart.div).append('div').datum(thisChart);
-
- thisChart.events = {
- onInit: function onInit() {},
- onLayout: function onLayout() {},
- onPreprocess: function onPreprocess() {},
- onDatatransform: function onDatatransform() {},
- onDraw: function onDraw() {},
- onResize: function onResize() {},
- onDestroy: function onDestroy() {}
- };
-
- thisChart.on = function(event, callback) {
- var possible_events = [
- 'init',
- 'layout',
- 'preprocess',
- 'datatransform',
- 'draw',
- 'resize',
- 'destroy'
- ];
- if (possible_events.indexOf(event) < 0) {
- return;
- }
- if (callback) {
- thisChart.events['on' + event.charAt(0).toUpperCase() + event.slice(1)] = callback;
- }
- };
-
- //increment thisChart count to get unique thisChart id
- chartCount++;
-
- thisChart.id = chartCount;
-
- return thisChart;
- }
-
- function changeOption(option, value, callback, draw) {
- var _this = this;
-
- this.targets.forEach(function(target) {
- if (option instanceof Array) {
- option.forEach(function(o) {
- return _this.stringAccessor(target.config, o, value);
- });
- } else {
- _this.stringAccessor(target.config, option, value);
- }
- //call callback function if provided
- if (callback) {
- callback();
- }
- if (draw) target.draw();
- });
- }
-
- function checkRequired$1(dataset) {
- if (!dataset[0] || !this.config.inputs) return;
-
- var colNames = d3.keys(dataset[0]);
-
- this.config.inputs.forEach(function(input, i) {
- if (input.type === 'subsetter' && colNames.indexOf(input.value_col) === -1)
- throw new Error(
- 'Error in settings object: the value "' +
- input.value_col +
- '" does not match any column in the provided dataset.'
- );
-
- //Draw the chart when a control changes unless the user specifies otherwise.
- input.draw = input.draw === undefined ? true : input.draw;
- });
- }
-
- function controlUpdate() {
- var _this = this;
-
- if (this.config.inputs && this.config.inputs.length && this.config.inputs[0])
- this.config.inputs.forEach(function(input) {
- return _this.makeControlItem(input);
- });
- }
-
- function destroy$1() {
- //unmount controls wrapper
- this.wrap.remove();
- }
-
- function init$1(data) {
- this.data = data;
- if (!this.config.builder) this.checkRequired(this.data);
- this.layout();
- }
-
- function layout$1() {
- this.wrap.selectAll('*').remove();
- this.ready = true;
- this.controlUpdate();
- }
-
- function makeControlItem(control) {
- var control_wrap = this.wrap
- .append('div')
- .attr('class', 'control-group')
- .classed('inline', control.inline)
- .datum(control);
-
- //Add control label span.
- var ctrl_label = control_wrap
- .append('span')
- .attr('class', 'wc-control-label')
- .text(control.label);
-
- //Add control _Required_ text to control label span.
- if (control.required)
- ctrl_label.append('span').attr('class', 'label label-required').text('Required');
-
- //Add control description span.
- control_wrap.append('span').attr('class', 'span-description').text(control.description);
-
- if (control.type === 'text') {
- this.makeTextControl(control, control_wrap);
- } else if (control.type === 'number') {
- this.makeNumberControl(control, control_wrap);
- } else if (control.type === 'list') {
- this.makeListControl(control, control_wrap);
- } else if (control.type === 'dropdown') {
- this.makeDropdownControl(control, control_wrap);
- } else if (control.type === 'btngroup') {
- this.makeBtnGroupControl(control, control_wrap);
- } else if (control.type === 'checkbox') {
- this.makeCheckboxControl(control, control_wrap);
- } else if (control.type === 'radio') {
- this.makeRadioControl(control, control_wrap);
- } else if (control.type === 'subsetter') {
- this.makeSubsetterControl(control, control_wrap);
- } else {
- throw new Error(
- 'Each control must have a type! Choose from: "text", "number", "list", "dropdown", "btngroup", "checkbox", "radio", or "subsetter".'
- );
- }
- }
-
- function makeBtnGroupControl(control, control_wrap) {
- var _this = this;
-
- var option_data = control.values ? control.values : d3.keys(this.data[0]);
-
- var btn_wrap = control_wrap.append('div').attr('class', 'btn-group');
-
- var changers = btn_wrap
- .selectAll('button')
- .data(option_data)
- .enter()
- .append('button')
- .attr('class', 'btn btn-default btn-sm')
- .text(function(d) {
- return d;
- })
- .classed('btn-primary', function(d) {
- return _this.stringAccessor(_this.targets[0].config, control.option) === d;
- });
-
- changers.on('click', function(d) {
- changers.each(function(e) {
- d3.select(this).classed('btn-primary', e === d);
- });
- _this.changeOption(control.option, d, control.callback, control.draw);
- });
- }
-
- function makeCheckboxControl(control, control_wrap) {
- var _this = this;
-
- var changer = control_wrap
- .append('input')
- .attr('type', 'checkbox')
- .attr('class', 'changer')
- .datum(control)
- .property('checked', function(d) {
- return _this.stringAccessor(_this.targets[0].config, control.option);
- });
-
- changer.on('change', function(d) {
- var value = changer.property('checked');
- _this.changeOption(d.option, value, control.callback, control.draw);
- });
- }
-
- function makeDropdownControl(control, control_wrap) {
- var _this = this;
-
- var mainOption = control.option || control.options[0];
- var changer = control_wrap
- .append('select')
- .attr('class', 'changer')
- .attr('multiple', control.multiple ? true : null)
- .datum(control);
-
- var opt_values = control.values && control.values instanceof Array
- ? control.values
- : control.values
- ? d3
- .set(
- this.data.map(function(m) {
- return m[_this.targets[0].config[control.values]];
- })
- )
- .values()
- : d3.keys(this.data[0]);
-
- if (!control.require || control.none) {
- opt_values.unshift('None');
- }
-
- var options = changer
- .selectAll('option')
- .data(opt_values)
- .enter()
- .append('option')
- .text(function(d) {
- return d;
- })
- .property('selected', function(d) {
- return _this.stringAccessor(_this.targets[0].config, mainOption) === d;
- });
-
- changer.on('change', function(d) {
- var value = changer.property('value') === 'None' ? null : changer.property('value');
-
- if (control.multiple) {
- value = options
- .filter(function(f) {
- return d3.select(this).property('selected');
- })[0]
- .map(function(m) {
- return d3.select(m).property('value');
- })
- .filter(function(f) {
- return f !== 'None';
- });
- }
-
- if (control.options) {
- _this.changeOption(control.options, value, control.callback, control.draw);
- } else {
- _this.changeOption(control.option, value, control.callback, control.draw);
- }
- });
-
- return changer;
- }
-
- function makeListControl(control, control_wrap) {
- var _this = this;
-
- var changer = control_wrap
- .append('input')
- .attr('type', 'text')
- .attr('class', 'changer')
- .datum(control)
- .property('value', function(d) {
- return _this.stringAccessor(_this.targets[0].config, control.option);
- });
-
- changer.on('change', function(d) {
- var value = changer.property('value')
- ? changer.property('value').split(',').map(function(m) {
- return m.trim();
- })
- : null;
- _this.changeOption(control.option, value, control.callback, control.draw);
- });
- }
-
- function makeNumberControl(control, control_wrap) {
- var _this = this;
-
- var changer = control_wrap
- .append('input')
- .attr('type', 'number')
- .attr('min', control.min !== undefined ? control.min : 0)
- .attr('max', control.max)
- .attr('step', control.step || 1)
- .attr('class', 'changer')
- .datum(control)
- .property('value', function(d) {
- return _this.stringAccessor(_this.targets[0].config, control.option);
- });
-
- changer.on('change', function(d) {
- var value = +changer.property('value');
- _this.changeOption(control.option, value, control.callback, control.draw);
- });
- }
-
- function makeRadioControl(control, control_wrap) {
- var _this = this;
-
- var changers = control_wrap
- .selectAll('label')
- .data(control.values || d3.keys(this.data[0]))
- .enter()
- .append('label')
- .attr('class', 'radio')
- .text(function(d, i) {
- return control.relabels ? control.relabels[i] : d;
- })
- .append('input')
- .attr('type', 'radio')
- .attr('class', 'changer')
- .attr('name', control.option.replace('.', '-') + '-' + this.targets[0].id)
- .property('value', function(d) {
- return d;
- })
- .property('checked', function(d) {
- return _this.stringAccessor(_this.targets[0].config, control.option) === d;
- });
-
- changers.on('change', function(d) {
- var value = null;
- changers.each(function(c) {
- if (d3.select(this).property('checked')) {
- value = d3.select(this).property('value') === 'none' ? null : c;
- }
- });
- _this.changeOption(control.option, value, control.callback, control.draw);
- });
- }
-
- function makeSubsetterControl(control, control_wrap) {
- var targets = this.targets; // associated charts and tables.
-
- //dropdown selection
- var changer = control_wrap
- .append('select')
- .classed('changer', true)
- .attr('multiple', control.multiple ? true : null)
- .datum(control);
-
- //dropdown option data
- var option_data = control.values
- ? control.values
- : d3
- .set(
- this.data
- .map(function(m) {
- return m[control.value_col];
- })
- .filter(function(f) {
- return f;
- })
- )
- .values()
- .sort(naturalSorter); // only sort when values are derived
-
- //initial dropdown option
- control.start = control.start ? control.start : control.loose ? option_data[0] : null;
-
- //conditionally add All option
- if (!control.multiple && !control.start) {
- option_data.unshift('All');
- control.all = true;
- } else {
- control.all = false;
- }
-
- //what does loose mean?
- control.loose = !control.loose && control.start ? true : control.loose;
-
- //dropdown options selection
- var options = changer
- .selectAll('option')
- .data(option_data)
- .enter()
- .append('option')
- .text(function(d) {
- return d;
- })
- .property('selected', function(d) {
- return d === control.start;
- });
-
- //define filter object for each associated target
- targets.forEach(function(e) {
- var match = e.filters
- .slice()
- .map(function(m) {
- return m.col === control.value_col;
- })
- .indexOf(true);
- if (match > -1) {
- e.filters[match] = {
- col: control.value_col,
- val: control.start ? control.start : !control.multiple ? 'All' : option_data,
- index: 0,
- choices: option_data,
- loose: control.loose,
- all: control.all
- };
- } else {
- e.filters.push({
- col: control.value_col,
- val: control.start ? control.start : !control.multiple ? 'All' : option_data,
- index: 0,
- choices: option_data,
- loose: control.loose,
- all: control.all
- });
- }
- });
-
- function setSubsetter(target, obj) {
- var match = -1;
- target.filters.forEach(function(e, i) {
- if (e.col === obj.col) {
- match = i;
- }
- });
- if (match > -1) {
- target.filters[match] = obj;
- }
- }
-
- //add event listener to control
- changer.on('change', function(d) {
- if (control.multiple) {
- var values = options
- .filter(function(f) {
- return d3.select(this).property('selected');
- })[0]
- .map(function(m) {
- return d3.select(m).property('text');
- });
-
- var new_filter = {
- col: control.value_col,
- val: values,
- index: null, // could specify an array of indices but seems like a waste of resources give it doesn't inform anything without an overall 'All'
- choices: option_data,
- loose: control.loose,
- all: control.all
- };
- targets.forEach(function(e) {
- setSubsetter(e, new_filter);
- //call callback function if provided
- if (control.callback) {
- control.callback();
- }
- if (control.draw) e.draw();
- });
- } else {
- var value = d3.select(this).select('option:checked').property('text');
- var index = d3.select(this).select('option:checked').property('index');
- var _new_filter = {
- col: control.value_col,
- val: value,
- index: index,
- choices: option_data,
- loose: control.loose,
- all: control.all
- };
- targets.forEach(function(e) {
- setSubsetter(e, _new_filter);
- //call callback function if provided
- if (control.callback) {
- control.callback();
- }
- e.draw();
- });
- }
- });
- }
-
- function makeTextControl(control, control_wrap) {
- var _this = this;
-
- var changer = control_wrap
- .append('input')
- .attr('type', 'text')
- .attr('class', 'changer')
- .datum(control)
- .property('value', function(d) {
- return _this.stringAccessor(_this.targets[0].config, control.option);
- });
-
- changer.on('change', function(d) {
- var value = changer.property('value');
- _this.changeOption(control.option, value, control.callback, control.draw);
- });
- }
-
- function stringAccessor(o, s, v) {
- //adapted from http://jsfiddle.net/alnitak/hEsys/
- s = s.replace(/\[(\w+)\]/g, '.$1');
- s = s.replace(/^\./, '');
- var a = s.split('.');
- for (var i = 0, n = a.length; i < n; ++i) {
- var k = a[i];
- if (k in o) {
- if (i == n - 1 && v !== undefined) o[k] = v;
- o = o[k];
- } else {
- return;
- }
- }
- return o;
- }
-
- var controls = {
- changeOption: changeOption,
- checkRequired: checkRequired$1,
- controlUpdate: controlUpdate,
- destroy: destroy$1,
- init: init$1,
- layout: layout$1,
- makeControlItem: makeControlItem,
- makeBtnGroupControl: makeBtnGroupControl,
- makeCheckboxControl: makeCheckboxControl,
- makeDropdownControl: makeDropdownControl,
- makeListControl: makeListControl,
- makeNumberControl: makeNumberControl,
- makeRadioControl: makeRadioControl,
- makeSubsetterControl: makeSubsetterControl,
- makeTextControl: makeTextControl,
- stringAccessor: stringAccessor
- };
-
- function createControls() {
- var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'body';
- var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
-
- var thisControls = Object.create(controls);
-
- thisControls.div = element;
-
- thisControls.config = Object.create(config);
- thisControls.config.inputs = thisControls.config.inputs || [];
-
- thisControls.targets = [];
-
- if (config.location === 'bottom') {
- thisControls.wrap = d3.select(element).append('div').attr('class', 'wc-controls');
- } else {
- thisControls.wrap = d3
- .select(element)
- .insert('div', ':first-child')
- .attr('class', 'wc-controls');
- }
-
- thisControls.wrap.datum(thisControls);
-
- return thisControls;
- }
-
- function applyFilters() {
- var _this = this;
-
- //If there are filters, return a filtered data array of the raw data.
- //Otherwise return the raw data.
- if (
- this.filters &&
- this.filters.some(function(filter) {
- return (
- (typeof filter.val === 'string' &&
- !(filter.all === true && filter.index === 0)) ||
- (Array.isArray(filter.val) && filter.val.length < filter.choices.length)
- );
- })
- ) {
- this.data.filtered = this.data.raw;
- this.filters
- .filter(function(filter) {
- return (
- (typeof filter.val === 'string' &&
- !(filter.all === true && filter.index === 0)) ||
- (Array.isArray(filter.val) && filter.val.length < filter.choices.length)
- );
- })
- .forEach(function(filter) {
- _this.data.filtered = _this.data.filtered.filter(function(d) {
- return Array.isArray(filter.val)
- ? filter.val.indexOf(d[filter.col]) > -1
- : filter.val === d[filter.col];
- });
- });
- } else this.data.filtered = this.data.raw;
- }
-
- function updateDataObject() {
- this.data.raw = this.data.passed;
- this.data.filtered = this.data.passed;
- this.config.activePage = 0;
- this.config.startIndex = this.config.activePage * this.config.nRowsPerPage; // first row shown
- this.config.endIndex = this.config.startIndex + this.config.nRowsPerPage; // last row shown
- }
-
- function applySearchTerm(data) {
- var _this = this;
-
- if (this.searchable.searchTerm) {
- //Determine which rows contain input text.
- this.data.searched = this.data.filtered.filter(function(d) {
- var match = false;
-
- Object.keys(d)
- .filter(function(key) {
- return _this.config.cols.indexOf(key) > -1;
- })
- .forEach(function(var_name) {
- if (match === false) {
- var cellText = '' + d[var_name];
- match =
- cellText.toLowerCase().indexOf(_this.searchable.searchTerm) > -1;
- }
- });
-
- return match;
- });
- this.data.processing = this.data.searched;
- } else {
- //Otherwise delete previously searched data and set data to filtered data.
- delete this.data.searched;
- this.data.processing = this.data.filtered;
- }
- }
-
- /*------------------------------------------------------------------------------------------------\
- Check equality of two arrays (https://stackoverflow.com/questions/7837456/how-to-compare-arrays-in-javascript)
- \------------------------------------------------------------------------------------------------*/
-
- // Warn if overriding existing method
- if (Array.prototype.equals)
- console.warn(
- "Overriding existing Array.prototype.equals. Possible causes: New API defines the method, there's a framework conflict or you've got double inclusions in your code."
- );
- // attach the .equals method to Array's prototype to call it on any array
- Array.prototype.equals = function(array) {
- // if the other array is a falsy value, return
- if (!array) return false;
-
- // compare lengths - can save a lot of time
- if (this.length != array.length) return false;
-
- for (var i = 0, l = this.length; i < l; i++) {
- // Check if we have nested arrays
- if (this[i] instanceof Array && array[i] instanceof Array) {
- // recurse into the nested arrays
- if (!this[i].equals(array[i])) return false;
- } else if (this[i] != array[i]) {
- // Warning - two different object instances will never be equal: {x:20} != {x:20}
- return false;
- }
- }
- return true;
- };
- // Hide method from for-in loops
- Object.defineProperty(Array.prototype, 'equals', { enumerable: false });
-
- function checkFilters() {
- if (this.filters) {
- this.currentFilters = this.filters.map(function(filter) {
- return filter.val;
- });
-
- //Reset pagination if filters have changed.
- if (!this.currentFilters.equals(this.previousFilters)) {
- this.config.activePage = 0;
- this.config.startIndex = this.config.activePage * this.config.nRowsPerPage; // first row shown
- this.config.endIndex = this.config.startIndex + this.config.nRowsPerPage; // last row shown
- }
-
- this.previousFilters = this.currentFilters;
- }
- }
-
- function updateTableHeaders() {
- var _this = this;
-
- this.thead_cells = this.thead
- .select('tr')
- .selectAll('th')
- .data(this.config.headers, function(d) {
- return d;
- });
- this.thead_cells.exit().remove();
- this.thead_cells.enter().append('th');
- this.thead_cells
- .sort(function(a, b) {
- return _this.config.headers.indexOf(a) - _this.config.headers.indexOf(b);
- })
- .attr('class', function(d) {
- return _this.config.cols[_this.config.headers.indexOf(d)];
- }) // associate column header with column name
- .text(function(d) {
- return d;
- });
- }
-
- function drawTableBody() {
- var _this = this;
-
- var table = this;
-
- //Define table body rows.
- var rows = this.tbody.selectAll('tr').data(this.data.processing).enter().append('tr');
-
- //Define table body cells.
- var cells = rows.selectAll('td').data(function(d) {
- return _this.config.cols.map(function(key) {
- return { col: key, text: d[key] };
- });
- });
- cells.exit().remove();
- cells.enter().append('td');
- cells
- .sort(function(a, b) {
- return _this.config.cols.indexOf(a.col) - _this.config.cols.indexOf(b.col);
- })
- .attr('class', function(d) {
- return d.col;
- })
- .each(function(d) {
- var cell = d3.select(this);
-
- //Apply text in data as html or as plain text.
- if (table.config.as_html) {
- cell.html(d.text);
- } else {
- cell.text(d.text);
- }
- });
- }
-
- function dynamicLayout() {
- var widths = {
- table: this.table.select('thead').node().offsetWidth,
- top:
- this.wrap.select('.table-top .searchable-container').node().offsetWidth +
- this.wrap.select('.table-top .sortable-container').node().offsetWidth,
- bottom:
- this.wrap.select('.table-bottom .pagination-container').node().offsetWidth +
- this.wrap.select('.table-bottom .exportable-container').node().offsetWidth
- };
-
- if (
- widths.table < Math.max(widths.top, widths.bottom) &&
- this.config.layout === 'horizontal'
- ) {
- this.config.layout = 'vertical';
- this.wrap
- .style('display', 'inline-block')
- .selectAll('.table-top,.table-bottom')
- .style('display', 'inline-block')
- .selectAll('.interactivity')
- .style({
- display: 'block',
- clear: 'both'
- });
- } else if (
- widths.table >= Math.max(widths.top, widths.bottom) &&
- this.config.layout === 'vertical'
- ) {
- this.config.layout = 'horizontal';
- this.wrap
- .style('display', 'table')
- .selectAll('.table-top,.table-bottom')
- .style('display', 'block')
- .selectAll('.interactivity')
- .style({
- display: 'inline-block',
- float: function float() {
- return d3.select(this).classed('searchable-container') ||
- d3.select(this).classed('pagination-container')
- ? 'right'
- : null;
- },
- clear: null
- });
- }
- }
-
- function draw$1(passed_data) {
- var _this = this;
-
- var table = this;
- var config = this.config;
-
- this.data.passed = passed_data; // make passed data available on preprocess
- this.events.onPreprocess.call(this);
-
- if (!passed_data)
- //Apply filters if data is not passed to table.draw().
- applyFilters.call(this);
- else
- //Otherwise update data object.
- updateDataObject.call(this);
-
- //Compare current filter settings to previous filter settings, if any.
- checkFilters.call(this);
-
- //Filter data on search term if it exists and set data to searched data.
- applySearchTerm.call(this);
-
- this.searchable.wrap
- .select('.nNrecords')
- .text(
- this.data.processing.length === this.data.raw.length
- ? this.data.raw.length + ' records displayed'
- : this.data.processing.length +
- '/' +
- this.data.raw.length +
- ' records displayed'
- );
-
- //Update table headers.
- updateTableHeaders.call(this);
-
- //Clear table body rows.
- this.tbody.selectAll('tr').remove();
-
- //Print a note that no data was selected for empty tables.
- if (this.data.processing.length === 0) {
- this.tbody
- .append('tr')
- .classed('no-data', true)
- .append('td')
- .attr('colspan', this.config.cols.length)
- .text('No data selected.');
-
- //Bind table filtered/searched data to table container.
- this.data.current = this.data.processing;
- this.table.datum(this.table.current);
-
- //Add export.
- if (this.config.exportable)
- this.config.exports.forEach(function(fmt) {
- _this.exportable.exports[fmt].call(_this, _this.data.processing);
- });
-
- //Add pagination.
- if (this.config.pagination)
- this.pagination.addPagination.call(this, this.data.processing);
- } else {
- //Sort data.
- if (this.config.sortable) {
- this.thead.selectAll('th').on('click', function(header) {
- table.sortable.onClick.call(table, this, header);
- });
-
- if (this.sortable.order.length)
- this.sortable.sortData.call(this, this.data.processing);
- }
-
- //Bind table filtered/searched data to table container.
- this.data.current = this.data.processing;
- this.table.datum(this.data.current);
-
- //Add export.
- if (this.config.exportable)
- this.config.exports.forEach(function(fmt) {
- _this.exportable.exports[fmt].call(_this, _this.data.processing);
- });
-
- //Add pagination.
- if (this.config.pagination) {
- this.pagination.addPagination.call(this, this.data.processing);
-
- //Apply pagination.
- this.data.processing = this.data.processing.filter(function(d, i) {
- return _this.config.startIndex <= i && i < _this.config.endIndex;
- });
- }
-
- //Define table body rows.
- drawTableBody.call(this);
- }
-
- //Alter table layout if table is narrower than table top or bottom.
- if (this.config.dynamicPositioning) {
- dynamicLayout.call(this);
- }
-
- this.events.onDraw.call(this);
- }
-
- function layout$2() {
- var context = this;
-
- this.searchable.wrap = this.wrap
- .select('.table-top')
- .append('div')
- .classed('interactivity searchable-container', true)
- .classed('hidden', !this.config.searchable);
- this.searchable.wrap.append('div').classed('search', true);
- this.searchable.wrap
- .select('.search')
- .append('input')
- .classed('search-box', true)
- .attr('placeholder', 'Search')
- .on('input', function() {
- context.searchable.searchTerm = this.value.toLowerCase() || null;
- context.config.activePage = 0;
- context.config.startIndex = context.config.activePage * context.config.nRowsPerPage; // first row shown
- context.config.endIndex = context.config.startIndex + context.config.nRowsPerPage; // last row shown
- context.draw();
- });
- this.searchable.wrap.select('.search').append('span').classed('nNrecords', true);
- }
-
- function searchable() {
- return {
- layout: layout$2
- };
- }
-
- function layout$3() {
- var _this = this;
-
- this.exportable.wrap = this.wrap
- .select('.table-bottom')
- .append('div')
- .classed('interactivity exportable-container', true)
- .classed('hidden', !this.config.exportable);
-
- this.exportable.wrap.append('span').text('Export:');
-
- if (this.config.exports && this.config.exports.length)
- this.config.exports.forEach(function(fmt) {
- _this.exportable.wrap
- .append('a')
- .classed('wc-button export', true)
- .attr({
- id: fmt
- })
- .style(
- !_this.test && navigator.msSaveBlob
- ? {
- cursor: 'pointer',
- 'text-decoration': 'underline',
- color: 'blue'
- }
- : null
- )
- .text(fmt.toUpperCase());
- });
- }
-
- function download(fileType, data) {
- //transform blob array into a blob of characters
- var blob = new Blob(data, {
- type: fileType === 'csv'
- ? 'text/csv;charset=utf-8;'
- : fileType === 'xlsx'
- ? 'application/octet-stream'
- : console.warn('File type not supported: ' + fileType)
- });
- var fileName =
- 'webchartsTableExport_' +
- d3.time.format('%Y-%m-%dT%H-%M-%S')(new Date()) +
- '.' +
- fileType;
- var link = this.wrap.select('.export#' + fileType);
-
- if (navigator.msSaveBlob)
- //IE
- navigator.msSaveBlob(blob, fileName);
- else if (link.node().download !== undefined) {
- //21st century browsers
- var url = URL.createObjectURL(blob);
- link.node().setAttribute('href', url);
- link.node().setAttribute('download', fileName);
- }
- }
-
- function csv(data) {
- var _this = this;
-
- this.wrap.select('.export#csv').on('click', function() {
- var CSVarray = [];
-
- //add headers to CSV array
- var headers = _this.config.headers.map(function(header) {
- return '"' + header.replace(/"/g, '""') + '"';
- });
- CSVarray.push(headers);
-
- //add rows to CSV array
- data.forEach(function(d, i) {
- var row = _this.config.cols.map(function(col) {
- var value = d[col];
-
- if (typeof value === 'string') value = value.replace(/"/g, '""');
-
- return '"' + value + '"';
- });
-
- CSVarray.push(row);
- });
-
- //Download .csv file.
- download.call(_this, 'csv', [CSVarray.join('\n')]);
- });
- }
-
- function xlsx(data) {
- var _this = this;
-
- this.wrap.select('.export#xlsx').on('click', function() {
- var sheetName = 'Selected Data';
- var options = {
- bookType: 'xlsx',
- bookSST: true,
- type: 'binary'
- };
- var arrayOfArrays = data.map(function(d) {
- return Object.keys(d)
- .filter(function(key) {
- return _this.config.cols.indexOf(key) > -1;
- })
- .map(function(key) {
- return d[key];
- });
- }); // convert data from array of objects to array of arrays.
- var workbook = {
- SheetNames: [sheetName],
- Sheets: {}
- };
- var cols = [];
-
- //Convert headers and data from array of arrays to sheet.
- workbook.Sheets[sheetName] = XLSX.utils.aoa_to_sheet(
- [_this.config.headers].concat(arrayOfArrays)
- );
-
- //Add filters to spreadsheet.
- workbook.Sheets[sheetName]['!autofilter'] = {
- ref: 'A1:' + String.fromCharCode(64 + _this.config.cols.length) + (data.length + 1)
- };
-
- //Define column widths in spreadsheet.
- _this.table.selectAll('thead tr th').each(function() {
- cols.push({ wpx: this.offsetWidth });
- });
- workbook.Sheets[sheetName]['!cols'] = cols;
-
- var xlsx = XLSX.write(workbook, options);
- var s2ab = function s2ab(s) {
- var buffer = new ArrayBuffer(s.length),
- view = new Uint8Array(buffer);
-
- for (var i = 0; i !== s.length; ++i) {
- view[i] = s.charCodeAt(i) & 0xff;
- }
- return buffer;
- }; // convert spreadsheet to binary or something, i don't know
-
- //Download .xlsx file.
- download.call(_this, 'xlsx', [s2ab(xlsx)]);
- });
- }
-
- var exports$1 = {
- csv: csv,
- xlsx: xlsx
- };
-
- function exportable() {
- return {
- layout: layout$3,
- exports: exports$1
- };
- }
-
- function layout$4() {
- //Add sort container.
- this.sortable.wrap = this.wrap
- .select('.table-top')
- .append('div')
- .classed('interactivity sortable-container', true)
- .classed('hidden', !this.config.sortable);
- this.sortable.wrap
- .append('div')
- .classed('instruction', true)
- .text('Click column headers to sort.');
- }
-
- function onClick(th, header) {
- var context = this,
- selection = d3.select(th),
- col = this.config.cols[this.config.headers.indexOf(header)];
-
- //Check if column is already a part of current sort order.
- var sortItem = this.sortable.order.filter(function(item) {
- return item.col === col;
- })[0];
-
- //If it isn't, add it to sort order.
- if (!sortItem) {
- sortItem = {
- col: col,
- direction: 'ascending',
- wrap: this.sortable.wrap
- .append('div')
- .datum({ key: col })
- .classed('wc-button sort-box', true)
- .text(header)
- };
- sortItem.wrap.append('span').classed('sort-direction', true).html('↓');
- sortItem.wrap.append('span').classed('remove-sort', true).html('❌');
- this.sortable.order.push(sortItem);
- } else {
- //Otherwise reverse its sort direction.
- sortItem.direction = sortItem.direction === 'ascending' ? 'descending' : 'ascending';
- sortItem.wrap
- .select('span.sort-direction')
- .html(sortItem.direction === 'ascending' ? '↓' : '↑');
- }
-
- //Hide sort instructions.
- this.sortable.wrap.select('.instruction').classed('hidden', true);
-
- //Add sort container deletion functionality.
- this.sortable.order.forEach(function(item, i) {
- item.wrap.on('click', function(d) {
- //Remove column's sort container.
- d3.select(this).remove();
-
- //Remove column from sort.
- context.sortable.order.splice(
- context.sortable.order
- .map(function(d) {
- return d.col;
- })
- .indexOf(d.key),
- 1
- );
-
- //Display sorting instruction.
- context.sortable.wrap
- .select('.instruction')
- .classed('hidden', context.sortable.order.length);
-
- //Redraw chart.
- context.draw();
- });
- });
-
- //Redraw chart.
- this.draw();
- }
-
- function sortData(data) {
- var _this = this;
-
- data = data.sort(function(a, b) {
- var order = 0;
-
- _this.sortable.order.forEach(function(item) {
- var aCell = a[item.col],
- bCell = b[item.col];
-
- if (order === 0) {
- if (
- (item.direction === 'ascending' && aCell < bCell) ||
- (item.direction === 'descending' && aCell > bCell)
- )
- order = -1;
- else if (
- (item.direction === 'ascending' && aCell > bCell) ||
- (item.direction === 'descending' && aCell < bCell)
- )
- order = 1;
- }
- });
-
- return order;
- });
- }
-
- function sortable() {
- return {
- layout: layout$4,
- onClick: onClick,
- sortData: sortData,
- order: []
- };
- }
-
- function layout$5() {
- this.pagination.wrap = this.wrap
- .select('.table-bottom')
- .append('div')
- .classed('interactivity pagination-container', true)
- .classed('hidden', !this.config.pagination);
- }
-
- function updatePagination() {
- var _this = this;
-
- //Reset pagination.
- this.pagination.links.classed('active', false);
-
- //Set to active the selected page link.
- var activePage = this.pagination.links
- .filter(function(link) {
- return +link.rel === +_this.config.activePage;
- })
- .classed('active', true);
-
- //Define and draw selected page.
- this.config.startIndex = this.config.activePage * this.config.nRowsPerPage;
- this.config.endIndex = this.config.startIndex + this.config.nRowsPerPage;
-
- //Redraw table.
- this.draw();
- }
-
- function addLinks() {
- var _this = this;
-
- //Count rows.
-
- this.pagination.wrap.selectAll('a,span').remove();
-
- var _loop = function _loop(i) {
- _this.pagination.wrap
- .append('a')
- .datum({ rel: i })
- .attr({
- rel: i
- })
- .text(i + 1)
- .classed('wc-button page-link', true)
- .classed('active', function(d) {
- return d.rel == _this.config.activePage;
- })
- .classed('hidden', function() {
- return _this.config.activePage < _this.config.nPageLinksDisplayed
- ? i >= _this.config.nPageLinksDisplayed // first nPageLinksDisplayed pages
- : _this.config.activePage >=
- _this.config.nPages - _this.config.nPageLinksDisplayed
- ? i < _this.config.nPages - _this.config.nPageLinksDisplayed // last nPageLinksDisplayed pages
- : i <
- _this.config.activePage -
- (Math.ceil(_this.config.nPageLinksDisplayed / 2) - 1) ||
- _this.config.activePage + _this.config.nPageLinksDisplayed / 2 < i; // nPageLinksDisplayed < activePage or activePage < (nPages - nPageLinksDisplayed)
- });
- };
-
- for (var i = 0; i < this.config.nPages; i++) {
- _loop(i);
- }
-
- this.pagination.links = this.pagination.wrap.selectAll('a.page-link');
- }
-
- function addArrows() {
- var prev = this.config.activePage - 1,
- next = this.config.activePage + 1;
- if (prev < 0) prev = 0; // nothing before the first page
- if (next >= this.config.nPages) next = this.config.nPages - 1; // nothing after the last page
-
- /**-------------------------------------------------------------------------------------------\
- Left side
- \-------------------------------------------------------------------------------------------**/
-
- this.pagination.wrap
- .insert('span', ':first-child')
- .classed('dot-dot-dot', true)
- .text('...')
- .classed('hidden', this.config.activePage < this.config.nPageLinksDisplayed);
-
- this.pagination.prev = this.pagination.wrap
- .insert('a', ':first-child')
- .classed('wc-button arrow-link wc-left', true)
- .classed('hidden', this.config.activePage == 0)
- .attr({
- rel: prev
- })
- .text('<');
-
- this.pagination.doublePrev = this.pagination.wrap
- .insert('a', ':first-child')
- .classed('wc-button arrow-link wc-left double', true)
- .classed('hidden', this.config.activePage == 0)
- .attr({
- rel: 0
- })
- .text('<<');
-
- /**-------------------------------------------------------------------------------------------\
- Right side
- \-------------------------------------------------------------------------------------------**/
-
- this.pagination.wrap
- .append('span')
- .classed('dot-dot-dot', true)
- .text('...')
- .classed(
- 'hidden',
- this.config.activePage >=
- Math.max(
- this.config.nPageLinksDisplayed,
- this.config.nPages - this.config.nPageLinksDisplayed
- ) || this.config.nPages <= this.config.nPageLinksDisplayed
- );
- this.pagination.next = this.pagination.wrap
- .append('a')
- .classed('wc-button arrow-link wc-right', true)
- .classed(
- 'hidden',
- this.config.activePage == this.config.nPages - 1 || this.config.nPages == 0
- )
- .attr({
- rel: next
- })
- .text('>');
-
- this.pagination.doubleNext = this.pagination.wrap
- .append('a')
- .classed('wc-button arrow-link wc-right double', true)
- .classed(
- 'hidden',
- this.config.activePage == this.config.nPages - 1 || this.config.nPages == 0
- )
- .attr({
- rel: this.config.nPages - 1
- })
- .text('>>');
-
- this.pagination.arrows = this.pagination.wrap.selectAll('a.arrow-link');
- this.pagination.doubleArrows = this.pagination.wrap.selectAll('a.double-arrow-link');
- }
-
- function addPagination(data) {
- var context = this;
-
- //Calculate number of pages needed and create a link for each page.
- this.config.nRows = data.length;
- this.config.nPages = Math.ceil(this.config.nRows / this.config.nRowsPerPage);
-
- //hide the pagination if there is only one page
- this.config.paginationHidden = this.config.nPages === 1;
- this.pagination.wrap.classed('hidden', this.config.paginationHidden);
-
- //Render page links.
- addLinks.call(this);
-
- //Render a different page on click.
- this.pagination.links.on('click', function() {
- context.config.activePage = +d3.select(this).attr('rel');
- updatePagination.call(context);
- });
-
- //Render arrow links.
- addArrows.call(this);
-
- //Render a different page on click.
- this.pagination.arrows.on('click', function() {
- if (context.config.activePage !== +d3.select(this).attr('rel')) {
- context.config.activePage = +d3.select(this).attr('rel');
- context.pagination.prev.attr(
- 'rel',
- context.config.activePage > 0 ? context.config.activePage - 1 : 0
- );
- context.pagination.next.attr(
- 'rel',
- context.config.activePage < context.config.nPages
- ? context.config.activePage + 1
- : context.config.nPages - 1
- );
- updatePagination.call(context);
- }
- });
-
- //Render a different page on click.
- this.pagination.doubleArrows.on('click', function() {
- context.config.activePage = +d3.select(this).attr('rel');
- updatePagination.call(context);
- });
-
- return {
- addLinks: addLinks,
- addArrows: addArrows,
- updatePagination: updatePagination
- };
- }
-
- function pagination() {
- this.config.nRows = this.data.raw.length; // total number of rows, i.e. the length of the data file
- this.config.nPages = Math.ceil(this.config.nRows / this.config.nRowsPerPage); // total number of pages given number of rows
- this.config.activePage = 0; // current page, 0-indexed
- this.config.startIndex = this.config.activePage * this.config.nRowsPerPage; // first row shown
- this.config.endIndex = this.config.startIndex + this.config.nRowsPerPage; // last row shown
- this.config.paginationHidden = this.config.nPages == 1;
- return {
- layout: layout$5,
- addPagination: addPagination
- };
- }
-
- function init$2(data) {
- var _this = this;
-
- var test = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
-
- this.test = test;
-
- if (d3.select(this.div).select('.loader').empty()) {
- d3
- .select(this.div)
- .insert('div', ':first-child')
- .attr('class', 'loader')
- .selectAll('.blockG')
- .data(d3.range(8))
- .enter()
- .append('div')
- .attr('class', function(d) {
- return 'blockG rotate' + (d + 1);
- });
- }
-
- //Define default settings.
- this.setDefaults.call(this, data[0]);
-
- //Assign classes to container element.
- this.wrap.classed('wc-chart', true).classed('wc-table', this.config.applyCSS);
-
- //Define data object.
- this.data = {
- raw: data
- };
-
- //Attach searchable object to table object.
- this.searchable = searchable.call(this);
-
- //Attach sortable object to table object.
- this.sortable = sortable.call(this);
-
- //Attach pagination object to table object.
- this.pagination = pagination.call(this);
-
- //Attach pagination object to table object.
- this.exportable = exportable.call(this);
-
- var startup = function startup(data) {
- //connect this table and its controls, if any
- if (_this.controls) {
- _this.controls.targets.push(_this);
- if (!_this.controls.ready) {
- _this.controls.init(_this.data.raw);
- } else {
- _this.controls.layout();
- }
- }
-
- //make sure container is visible (has height and width) before trying to initialize
- var visible = d3.select(_this.div).property('offsetWidth') > 0 || test;
- if (!visible) {
- console.warn(
- 'The table cannot be initialized inside an element with 0 width. The table will be initialized as soon as the container element is given a width > 0.'
- );
- var onVisible = setInterval(function(i) {
- var visible_now = d3.select(_this.div).property('offsetWidth') > 0;
- if (visible_now) {
- _this.layout();
- _this.wrap.datum(_this);
- _this.draw();
- clearInterval(onVisible);
- }
- }, 500);
- } else {
- _this.layout();
- _this.wrap.datum(_this);
- _this.draw();
- }
- };
-
- this.events.onInit.call(this);
- if (this.data.raw.length) {
- this.checkRequired(this.data.raw);
- }
- startup(data);
-
- return this;
- }
-
- function layout$6() {
- //Clear loading indicator.
- d3.select(this.div).select('.loader').remove();
-
- //Attach container before table.
- this.wrap.append('div').classed('table-top', true);
-
- //Attach search container.
- this.searchable.layout.call(this);
-
- //Attach sort container.
- this.sortable.layout.call(this);
-
- //Attach table to DOM.
- this.table = this.wrap.append('table').classed('table', this.config.bootstrap); // apply class to incorporate bootstrap styling
- this.thead = this.table.append('thead');
- this.thead.append('tr');
- this.tbody = this.table.append('tbody');
-
- //Attach container after table.
- this.wrap.append('div').classed('table-bottom', true);
-
- //Attach pagination container.
- this.pagination.layout.call(this);
-
- //Attach data export container.
- this.exportable.layout.call(this);
-
- //Call layout callback.
- this.events.onLayout.call(this);
- }
-
- function destroy$2() {
- var destroyControls = arguments.length > 0 && arguments[0] !== undefined
- ? arguments[0]
- : false;
-
- //run onDestroy callback
- this.events.onDestroy.call(this);
-
- //destroy controls
- if (destroyControls && this.controls) {
- this.controls.destroy();
- }
-
- //unmount chart wrapper
- this.wrap.remove();
- }
-
- function setDefault(setting) {
- var _default_ = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
-
- this.config[setting] = this.config[setting] !== undefined
- ? this.config[setting]
- : _default_;
- }
-
- function setDefaults$1(firstItem) {
- //Set data-driven defaults.
- if (this.config.cols instanceof Array && this.config.headers instanceof Array) {
- if (this.config.cols.length === 0) delete this.config.cols;
- if (
- this.config.headers.length === 0 ||
- this.config.headers.length !== this.config.cols.length
- )
- delete this.config.headers;
- }
-
- this.config.cols = this.config.cols || d3.keys(firstItem);
- this.config.headers = this.config.headers || this.config.cols;
- this.config.layout = 'horizontal'; // placeholder setting to align table components vertically or horizontally
-
- //Set all other defaults.
- setDefault.call(this, 'searchable');
- setDefault.call(this, 'exportable');
- setDefault.call(this, 'exports', ['csv']);
- setDefault.call(this, 'sortable');
- setDefault.call(this, 'pagination');
- setDefault.call(this, 'nRowsPerPage', 10);
- setDefault.call(this, 'nPageLinksDisplayed', 5);
- setDefault.call(this, 'applyCSS');
- setDefault.call(this, 'dynamicPositioning');
- }
-
- function transformData$1(processed_data) {
- var _this = this;
-
- //Transform data.
- this.data.processed = this.transformData(this.wrap.datum);
-
- if (!data) {
- return;
- }
-
- this.config.cols = this.config.cols || d3.keys(data[0]);
- this.config.headers = this.config.headers || this.config.cols;
-
- if (this.config.keep) {
- this.config.keep.forEach(function(e) {
- if (_this.config.cols.indexOf(e) === -1) {
- _this.config.cols.unshift(e);
- }
- });
- }
-
- var filtered = data;
-
- if (this.filters.length) {
- this.filters.forEach(function(e) {
- var is_array = e.val instanceof Array;
- filtered = filtered.filter(function(d) {
- if (is_array) {
- return e.val.indexOf(d[e.col]) !== -1;
- } else {
- return e.val !== 'All' ? d[e.col] === e.val : d;
- }
- });
- });
- }
-
- var slimmed = d3
- .nest()
- .key(function(d) {
- if (_this.config.row_per) {
- return _this.config.row_per
- .map(function(m) {
- return d[m];
- })
- .join(' ');
- } else {
- return d;
- }
- })
- .rollup(function(r) {
- if (_this.config.dataManipulate) {
- r = _this.config.dataManipulate(r);
- }
- var nuarr = r.map(function(m) {
- var arr = [];
- for (var x in m) {
- arr.push({ col: x, text: m[x] });
- }
- arr.sort(function(a, b) {
- return _this.config.cols.indexOf(a.col) - _this.config.cols.indexOf(b.col);
- });
- return { cells: arr, raw: m };
- });
- return nuarr;
- })
- .entries(filtered);
-
- this.data.current = slimmed.length ? slimmed : [{ key: null, values: [] }]; // dummy nested data array
-
- //Reset pagination.
- this.pagination.wrap.selectAll('*').remove();
-
- this.events.onDatatransform.call(this);
-
- /**-------------------------------------------------------------------------------------------\
- Code below associated with the former paradigm of a d3.nest() data array.
- \-------------------------------------------------------------------------------------------**/
-
- if (config.row_per) {
- var rev_order = config.row_per.slice(0).reverse();
- rev_order.forEach(function(e) {
- tbodies.sort(function(a, b) {
- return a.values[0].raw[e] - b.values[0].raw[e];
- });
- });
- }
-
- //Delete text from columns with repeated values?
- if (config.row_per) {
- rows
- .filter(function(f, i) {
- return i > 0;
- })
- .selectAll('td')
- .filter(function(f) {
- return config.row_per.indexOf(f.col) > -1;
- })
- .text('');
- }
-
- return this.data.current;
- }
-
- var table = Object.create(chart, {
- draw: { value: draw$1 },
- init: { value: init$2 },
- layout: { value: layout$6 },
- setDefaults: { value: setDefaults$1 },
- transformData: { value: transformData$1 },
- destroy: { value: destroy$2 }
- });
-
- function createTable() {
- var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'body';
- var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
- var controls = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
-
- var thisTable = Object.create(table);
-
- thisTable.div = element;
-
- thisTable.config = Object.create(config);
-
- thisTable.controls = controls;
-
- thisTable.filters = [];
-
- thisTable.required_cols = [];
-
- thisTable.wrap = d3.select(thisTable.div).append('div').datum(thisTable);
-
- thisTable.events = {
- onInit: function onInit() {},
- onLayout: function onLayout() {},
- onPreprocess: function onPreprocess() {},
- onDraw: function onDraw() {},
- onDestroy: function onDestroy() {}
- };
-
- thisTable.on = function(event, callback) {
- var possible_events = ['init', 'layout', 'preprocess', 'draw', 'destroy'];
- if (possible_events.indexOf(event) < 0) {
- return;
- }
- if (callback) {
- thisTable.events['on' + event.charAt(0).toUpperCase() + event.slice(1)] = callback;
- }
- };
-
- return thisTable;
- }
-
- function multiply(chart, data, split_by, order) {
- var test = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
-
- chart.wrap.classed('wc-layout wc-small-multiples', true).classed('wc-chart', false);
-
- //Define container for legend that overrides multiples' legends.
- chart.master_legend = chart.wrap.append('ul').attr('class', 'legend');
- chart.master_legend.append('span').classed('legend-title', true);
-
- //Instantiate multiples array.
- chart.multiples = [];
-
- function goAhead(data) {
- var split_vals = d3
- .set(
- data.map(function(m) {
- return m[split_by];
- })
- )
- .values()
- .filter(function(f) {
- return f;
- });
- if (order) {
- split_vals = split_vals.sort(function(a, b) {
- return d3.ascending(order.indexOf(a), order.indexOf(b));
- });
- }
- split_vals.forEach(function(e) {
- var mchart = createChart(chart.wrap.node(), chart.config, chart.controls);
- chart.multiples.push(mchart);
- mchart.parent = chart;
- mchart.events = chart.events;
- mchart.legend = chart.master_legend;
- mchart.filters.unshift({ col: split_by, val: e, choices: split_vals });
- mchart.wrap.insert('span', 'svg').attr('class', 'wc-chart-title').text(e);
- mchart.init(data, test);
- });
- }
-
- goAhead(data);
- }
-
- function getValType(data, variable) {
- var var_vals = d3
- .set(
- data.map(function(m) {
- return m[variable];
- })
- )
- .values();
- var vals_numbers = var_vals.filter(function(f) {
- return +f || +f === 0;
- });
-
- if (var_vals.length === vals_numbers.length && var_vals.length > 4) {
- return 'continuous';
- } else {
- return 'categorical';
- }
- }
-
- function lengthenRaw(data, columns) {
- var my_data = [];
-
- data.forEach(function(e) {
- columns.forEach(function(g) {
- var obj = Object.create(e);
- obj.wc_category = g;
- obj.wc_value = e[g];
- my_data.push(obj);
- });
- });
-
- return my_data;
- }
-
- var dataOps = {
- getValType: getValType,
- lengthenRaw: lengthenRaw,
- naturalSorter: naturalSorter,
- summarize: summarize
- };
-
- var index = {
- version: version,
- createChart: createChart,
- createControls: createControls,
- createTable: createTable,
- multiply: multiply,
- dataOps: dataOps
- };
-
- return index;
-});
diff --git a/inst/safetyGraphics_app/www/index.css b/inst/safetyGraphics_app/www/index.css
index f41b8623..1de852b0 100644
--- a/inst/safetyGraphics_app/www/index.css
+++ b/inst/safetyGraphics_app/www/index.css
@@ -1,5 +1,3 @@
-
-
.mapping-domain{
padding:1em;
border:1px solid black;
@@ -56,4 +54,4 @@ table.metatable.dataTable tr > td:last-of-type, table.metatable.trdataTable tr >
#dataSettings-previews .nav-tabs{
margin-bottom: 1em;
-}
\ No newline at end of file
+}
diff --git a/tests/testthat/test_mod_mappingSelect.R b/tests/testthat/test_mod_mappingSelect.R
index aeacdbcf..f119b91c 100644
--- a/tests/testthat/test_mod_mappingSelect.R
+++ b/tests/testthat/test_mod_mappingSelect.R
@@ -14,7 +14,7 @@ test_that("Inputs have expected values",{
})
test_that("Module server outputs the expected values",{
- empty<-'\"\"' #output value for empty string
+ empty<-""
expect_match(app$getValue("ex1"),empty)
expect_match(app$getValue("ex2"),"USUBJID")
expect_match(app$getValue("ex3"),empty)