From 4283d6eebaeaca6e9869706524c7cb2587b49061 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Wed, 10 Feb 2021 10:09:33 -0500 Subject: [PATCH 1/7] add support for multiple libpaths. fix #441 --- R/makeChartConfig.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/R/makeChartConfig.R b/R/makeChartConfig.R index f1bbfd71..9ed748ac 100644 --- a/R/makeChartConfig.R +++ b/R/makeChartConfig.R @@ -23,8 +23,10 @@ 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="/") + for(lib in .libPaths()){ + dirs<-paste(lib,'safetycharts','config', sep="/") + if(file.exists(dirs)) break + } } if(sourceFiles){ From 19bdf784ce948fe151d0dd256327fddcf55cc9ef Mon Sep 17 00:00:00 2001 From: jwildfire Date: Wed, 10 Feb 2021 10:21:31 -0500 Subject: [PATCH 2/7] fix capitalization. close #464. --- R/makeChartConfig.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/makeChartConfig.R b/R/makeChartConfig.R index 9ed748ac..19a380e7 100644 --- a/R/makeChartConfig.R +++ b/R/makeChartConfig.R @@ -24,7 +24,7 @@ makeChartConfig <- function(dirs, sourceFiles=TRUE){ # Use the charts settings saved in safetycharts if no path is provided. if(missing(dirs) || is.null(dirs)){ for(lib in .libPaths()){ - dirs<-paste(lib,'safetycharts','config', sep="/") + dirs<-paste(lib,'safetyCharts','config', sep="/") if(file.exists(dirs)) break } } From db3e3cb63f57d5297d8a59c148aa518a1c173b17 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Wed, 10 Feb 2021 10:52:28 -0500 Subject: [PATCH 3/7] fix index.css load --- R/app_ui.R | 8 +++++++- inst/safetyGraphics_app/www/index.css | 4 +--- 2 files changed, 8 insertions(+), 4 deletions(-) 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/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 +} From 2383d1eac67ff4e0ff581ac0ebb849932e35441f Mon Sep 17 00:00:00 2001 From: Spencer Childress Date: Tue, 9 Mar 2021 16:36:39 -0500 Subject: [PATCH 4/7] fix `data.frame(..., stringsAsFactors = FALSE)` bug --- R/mod_mappingColumn.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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]]()) From 903643e2fc0e23945cb7f254494dda8135831166 Mon Sep 17 00:00:00 2001 From: Spencer Childress Date: Tue, 23 Mar 2021 15:38:59 -0400 Subject: [PATCH 5/7] fix #500; fix #504 --- R/makeChartConfig.R | 8 +- R/mod_chartsTab.R | 3 +- R/mod_filterTab.R | 4 +- .../lib/ae-timelines-2.1.6/aeTimelines.js | 1158 ---- .../lib/aeexplorer-3.3.5/aeTable.css | 369 -- .../lib/aeexplorer-3.3.5/aeTable.js | 2329 ------- .../lib/hep-explorer-1.3.1/hepexplorer.js | 5691 ----------------- .../paneledOutlierExplorer.js | 2101 ------ .../safetyDeltaDelta.js | 1851 ------ .../safety-histogram-2.4.1/safetyHistogram.js | 2205 ------- .../safetyOutlierExplorer.js | 2478 ------- .../safetyResultsOverTime.js | 1866 ------ .../safetyShiftPlot.js | 1399 ---- 13 files changed, 10 insertions(+), 21452 deletions(-) delete mode 100644 inst/htmlwidgets/lib/ae-timelines-2.1.6/aeTimelines.js delete mode 100644 inst/htmlwidgets/lib/aeexplorer-3.3.5/aeTable.css delete mode 100644 inst/htmlwidgets/lib/aeexplorer-3.3.5/aeTable.js delete mode 100644 inst/htmlwidgets/lib/hep-explorer-1.3.1/hepexplorer.js delete mode 100644 inst/htmlwidgets/lib/paneled-outlier-explorer-1.1.4/paneledOutlierExplorer.js delete mode 100644 inst/htmlwidgets/lib/safety-delta-delta-1.0.0/safetyDeltaDelta.js delete mode 100644 inst/htmlwidgets/lib/safety-histogram-2.4.1/safetyHistogram.js delete mode 100644 inst/htmlwidgets/lib/safety-outlier-explorer-2.6.0/safetyOutlierExplorer.js delete mode 100644 inst/htmlwidgets/lib/safety-results-over-time-2.3.3/safetyResultsOverTime.js delete mode 100644 inst/htmlwidgets/lib/safety-shift-plot-2.1.3/safetyShiftPlot.js diff --git a/R/makeChartConfig.R b/R/makeChartConfig.R index 19a380e7..aae70c18 100644 --- a/R/makeChartConfig.R +++ b/R/makeChartConfig.R @@ -56,10 +56,16 @@ 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) }) - + charts <- charts[order(purrr::map_dbl(charts, function(chart) chart$order))] names(charts) <- yaml_files %>% file_path_sans_ext %>% basename 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/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 + - '' + - '
(n=' + - (chart.config.summary === 'participant' ? d.n : d.nEvents) + - ')' - ); - }) - .style('color', function(d) { - return chart.colorScale(d.key); - }) - .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; - } - }); - header2.append('th').attr('class', 'prevHeader'); - if (nGroups > 1 && chart.config.defaults.diffCol) { - header1.append('th').text('Difference Between Groups').attr('class', 'diffplot'); - header2.append('th').attr('class', 'diffplot axis'); - } - - //Prevalence scales - var allPercents = d3.merge( - chart.data.major.map(function(major) { - return d3.merge( - major.values.map(function(minor) { - return d3.merge( - minor.values.map(function(group) { - return [group.values.per]; - }) - ); - }) - ); - }) - ); - chart.percentScale = d3.scale - .linear() - .range([0, chart.config.plotSettings.w]) - .range([ - chart.config.plotSettings.margin.left, - chart.config.plotSettings.w - chart.config.plotSettings.margin.right - ]) - .domain([0, d3.max(allPercents)]); - - //Add Prevalence Axis - var percentAxis = d3.svg.axis().scale(chart.percentScale).orient('top').ticks(6); - - var prevAxis = chart.wrap - .select('th.prevHeader') - .append('svg') - .attr('height', '34px') - .attr('width', chart.config.plotSettings.w + 10) - .append('svg:g') - .attr('transform', 'translate(5,34)') - .attr('class', 'axis percent') - .call(percentAxis); - - //Difference Scale - if (chart.config.groups.length > 1) { - //Difference Scale - var allDiffs = d3.merge( - chart.data.major.map(function(major) { - return d3.merge( - major.values.map(function(minor) { - return d3.merge( - minor.differences.map(function(diff) { - return [diff.upper, diff.lower]; - }) - ); - }) - ); - }) - ); - - var minorDiffs = d3.merge( - chart.data.minor.map(function(m) { - return d3.merge( - m.values.map(function(m2) { - return d3.merge( - m2.differences.map(function(m3) { - return d3.merge([[m3.upper], [m3.lower]]); - }) - ); - }) - ); - }) - ); - - chart.diffScale = d3.scale - .linear() - .range([ - chart.config.plotSettings.diffMargin.left, - chart.config.plotSettings.w - chart.config.plotSettings.diffMargin.right - ]) - .domain(d3.extent(d3.merge([minorDiffs, allDiffs]))); - - //Difference Axis - var diffAxis = d3.svg.axis().scale(chart.diffScale).orient('top').ticks(8); - - var prevAxis = chart.wrap - .select('th.diffplot.axis') - .append('svg') - .attr('height', '34px') - .attr('width', chart.config.plotSettings.w + 10) - .append('svg:g') - .attr('transform', 'translate(5,34)') - .attr('class', 'axis') - .attr('class', 'percent') - .call(diffAxis); - } - - //////////////////////////// - // Add Rows to the table // - //////////////////////////// - - //Append a group of rows () for each major category. - var majorGroups = tab - .selectAll('tbody') - .data(chart.data.major, function(d) { - return d.key; - }) - .enter() - .append('tbody') - .attr('class', 'minorHidden') - .attr('class', function(d) { - return 'major-' + d.key.replace(/[^A-Za-z0-9]/g, ''); - }); - - //Append a row summarizing all minor categories for each major category. - var majorRows = majorGroups - .selectAll('tr') - .data( - function(d) { - return d.values; - }, - function(datum) { - return datum.key; - } - ) - .enter() - .append('tr') - .attr('class', 'major') - .each(function(d) { - var thisRow = d3.select(this); - chart.util.fillRow(thisRow, chart, d); - }); - - //Append rows for each minor category. - var majorGroups = tab.selectAll('tbody').data(chart.data.minor, function(d) { - return d.key; - }); - - var minorRows = majorGroups - .selectAll('tr') - .data( - function(d) { - return d.values; - }, - function(datum) { - return datum.key; - } - ) - .enter() - .append('tr') - .attr('class', 'minor') - .each(function(d) { - var thisRow = d3.select(this); - chart.util.fillRow(thisRow, chart, d); - }); - //Add a footer for overall rates. - tab - .append('tfoot') - .selectAll('tr') - .data(chart.data.any.length > 0 ? chart.data.any[0].values : []) - .enter() - .append('tr') - .each(function(d) { - var thisRow = d3.select(this); - chart.util.fillRow(thisRow, chart, d); - }); - - //Remove unwanted elements from the footer. - tab.selectAll('tfoot svg').remove(); - tab.select('tfoot i').remove(); - tab.select('tfoot td.controls span').text(''); - - ////////////////////////////////////////////////// - // Initialize event listeners for summary Table // - ////////////////////////////////////////////////// - - // Show cell counts on Mouseover/Mouseout of difference diamonds - chart.wrap - .selectAll('td.diffplot svg g path.diamond') - .on('mouseover', function(d) { - var currentRow = chart.wrap.selectAll('.SummaryTable tbody tr').filter(function(e) { - return ( - e.values[0].values.major === d.major && e.values[0].values.minor === d.minor - ); - }); - - //Display CI; - d3.select(this.parentNode).select('.ci').classed('wc-hidden', false); - - //show cell counts for selected groups - showCellCounts(chart, currentRow, d.group1); - showCellCounts(chart, currentRow, d.group2); - }) - .on('mouseout', function(d) { - d3.select(this.parentNode).select('.ci').classed('wc-hidden', true); //hide CI - chart.wrap.selectAll('.annote').remove(); //Delete annotations. - }); - - // Highlight rows on mouseover - chart.wrap - .selectAll('.SummaryTable tr') - .on('mouseover', function(d) { - d3.select(this).select('td.rowLabel').classed('highlight', true); - }) - .on('mouseout', function(d) { - d3.select(this).select('td.rowLabel').classed('highlight', false); - }); - - //Expand/collapse a section - chart.wrap.selectAll('tr.major').selectAll('td.controls').on('click', function(d) { - var current = d3.select(this.parentNode.parentNode); - var toggle = !current.classed('minorHidden'); - current.classed('minorHidden', toggle); - - d3 - .select(this) - .select('span') - .attr('title', toggle ? 'Expand' : 'Collapse') - .text(function() { - return toggle ? '+' : '-'; - }); - }); - - // Render the details table - chart.wrap.selectAll('td.rowLabel').on('click', function(d) { - //Update classes (row visibility handeled via css) - var toggle = !chart.wrap.select('.SummaryTable table').classed('summary'); - chart.wrap.select('.SummaryTable table').classed('summary', toggle); - chart.wrap.select('div.controls').selectAll('div').classed('wc-hidden', toggle); - - //Create/remove the participant level table - if (toggle) { - var major = d.values[0].values['major']; - var minor = d.values[0].values['minor']; - var detailTableSettings = { - major: major, - minor: minor - }; - chart.detailTable.init(chart, detailTableSettings); - } else { - chart.wrap.select('.DetailTable').remove(); - chart.wrap.select('div.closeDetailTable').remove(); - } - }); - } - - /*------------------------------------------------------------------------------------------------\ - Apply basic filters and toggles. - \------------------------------------------------------------------------------------------------*/ - - function toggleRows(chart) { - //Toggle minor rows. - var minorToggle = !chart.config.defaults.prefTerms; - chart.wrap.selectAll('.SummaryTable tbody').classed('minorHidden', minorToggle); - chart.wrap - .selectAll('.SummaryTable table tbody') - .select('tr.major td.controls span') - .text(minorToggle ? '+' : '-'); - - //Toggle Difference plots - var differenceToggle = false; - chart.wrap.selectAll('.SummaryTable .diffplot').classed('wc-hidden', differenceToggle); - - //Filter based on prevalence. - var filterVal = chart.wrap.select('div.controls input.rateFilter').property('value'); - chart.wrap.selectAll('div.SummaryTable table tbody').each(function(d) { - var allRows = d3.select(this).selectAll('tr'); - var filterRows = allRows.filter(function(d) { - var percents = d.values - .filter(function(d) { - //only keep the total column if groupColumns are hidden (otherwise keep all columns) - return chart.config.defaults.groupCols ? true : d.key == 'Total'; - }) - .map(function(element) { - return element.values.per; - }); - var maxPercent = d3.max(percents); - - return maxPercent < filterVal; - }); - filterRows.classed('filter', 'true'); - - d3 - .select(this) - .select('tr.major td.controls span') - .classed('wc-hidden', filterRows[0].length + 1 >= allRows[0].length); - }); - } - - /*------------------------------------------------------------------------------------------------\ - Define AETable object (the meat and potatoes). - \------------------------------------------------------------------------------------------------*/ - - var AETable = { - redraw: redraw, - wipe: wipe, - init: init$7, - toggleRows: toggleRows - }; - - function makeDetailData(chart, detailTableSettings) { - //convenience mappings - var major = detailTableSettings.major; - var minor = detailTableSettings.minor; - var vars = chart.config.variables; - - //Filter the raw data given the select major and/or minor category. - var details = chart.population_event_data.filter(function(d) { - var majorMatch = major === 'All' ? true : major === d[vars['major']]; - var minorMatch = minor === 'All' ? true : minor === d[vars['minor']]; - return majorMatch && minorMatch; - }); - - if (vars.details.length === 0) - vars.details = Object.keys(chart.population_data[0]).filter(function(d) { - return ['data_all', 'placeholderFlag'].indexOf(d) === -1; - }); - - //Keep only those columns specified in settings.variables.details append - //If provided with a details object use that to determine chosen - //variables and headers - var detailVars = vars.details; - var details = details.map(function(d) { - var current = {}; - detailVars.forEach(function(currentVar) { - if (currentVar.value_col) { - // only true if a details object is provided - currentVar.label // if label is provided, write over column name with label - ? (current[currentVar.label] = d[currentVar.value_col]) - : (current[currentVar.value_col] = d[currentVar.value_col]); - } else { - current[currentVar] = d[currentVar]; - } - }); - return current; - }); - - return details; - } - - function toggleControls(chart) { - //Details about current population filters - var filtered = chart.raw_event_data.length != chart.population_event_data.length; - if (filtered) { - chart.wrap - .select('div.controls') - .select('div.custom-filters') - .classed('wc-hidden', false) - .selectAll('select') - .property('disabled', 'disabled'); - chart.detailTable.head - .append('span') - .html(filtered ? 'The listing is filtered as shown:' : ''); - } - } - - function makeTitle(chart, detailData, detailTableSettings) { - //Add explanatory listing title. - chart.detailTable.head - .append('h4') - .html( - detailTableSettings.minor === 'All' - ? 'Details for ' + - detailData.length + - ' ' + - detailTableSettings.major + - ' records' - : 'Details for ' + - detailData.length + - ' ' + - detailTableSettings.minor + - ' (' + - detailTableSettings.major + - ') records' - ); - } - - function layout$1(chart) { - chart.detailTable.wrap = chart.wrap - .select('div.table-wrapper') - .append('div') - .attr('class', 'DetailTable'); - - chart.detailTable.head = chart.wrap - .select('div.table-wrapper') - .insert('div', '.controls') - .attr('class', 'DetailHeader'); - - //Add button to return to standard view. - var closeButton = chart.detailTable.head - .append('button') - .attr('class', 'closeDetailTable btn btn-primary'); - - closeButton.html( - ' Return to the Summary View' - ); - - closeButton.on('click', function() { - chart.wrap.select('.SummaryTable table').classed('summary', false); - chart.wrap.select('div.controls').selectAll('div').classed('wc-hidden', false); - chart.wrap - .select('div.controls') - .select('div.custom-filters') - .selectAll('select') - .property('disabled', ''); - chart.wrap.selectAll('.SummaryTable table tbody tr').classed('wc-active', false); - if (chart.config.defaults.webchartsDetailTable) { - chart.detailTable.table.destroy(); - } - chart.detailTable.wrap.remove(); - chart.detailTable.head.remove(); - }); - } - - function draw(chart, data) { - chart.detailTable.table = webCharts.createTable( - //chart.config.container + ' .aeExplorer .aeTable .table-wrapper .DetailTable', - chart.detailTable.wrap.node(), - {} - ); - chart.detailTable.table.init(data); - } - - function draw$1(chart, data) { - //Generate listing container. - var canvas = chart.detailTable.wrap; - var listing = canvas.append('table').attr('class', 'table'); - - //Append header to listing container. - var headerRow = listing.append('thead').append('tr'); - headerRow.selectAll('th').data(d3.keys(data[0])).enter().append('th').html(function(d) { - return d; - }); - - //Add rows to listing container. - var tbody = listing.append('tbody'); - var rows = tbody.selectAll('tr').data(data).enter().append('tr'); - - //Add data cells to rows. - var cols = rows - .selectAll('tr') - .data(function(d) { - return d3.values(d); - }) - .enter() - .append('td') - .html(function(d) { - return d; - }); - } - - /*------------------------------------------------------------------------------------------------\ - Generate data listing. - \------------------------------------------------------------------------------------------------*/ - - function init$8(chart, detailTableSettings) { - var detailData = makeDetailData(chart, detailTableSettings); - layout$1(chart); - makeTitle(chart, detailData, detailTableSettings); - toggleControls(chart); - - //initialize and draw the chart either using webcharts or raw D3 - if (chart.config.defaults.webchartsDetailTable) { - draw(chart, detailData); - } else { - draw$1(chart, detailData); - } - } - - /*------------------------------------------------------------------------------------------------\ - Define detail table object. - \------------------------------------------------------------------------------------------------*/ - - var detailTable = { - init: init$8 - }; - - function createChart() { - var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'body'; - var config = arguments[1]; - - var chart = { - element: element, - config: config, - init: init, - colorScale: colorScale, - layout: layout, - controls: controls, - AETable: AETable, - detailTable: detailTable, - util: util - }; - - return chart; - } - - var index = { - createChart: createChart - }; - - return index; -}); diff --git a/inst/htmlwidgets/lib/hep-explorer-1.3.1/hepexplorer.js b/inst/htmlwidgets/lib/hep-explorer-1.3.1/hepexplorer.js deleted file mode 100644 index fd495d73..00000000 --- a/inst/htmlwidgets/lib/hep-explorer-1.3.1/hepexplorer.js +++ /dev/null @@ -1,5691 +0,0 @@ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' - ? (module.exports = factory(require('webcharts'))) - : typeof define === 'function' && define.amd - ? define(['webcharts'], factory) - : (global.hepexplorer = factory(global.webCharts)); -})(this, function (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; - } - }); - } - - (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.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); - } - }); - }; - - 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 defineProperty = function (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; - }; - - /*------------------------------------------------------------------------------------------------\ - 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 settings() { - return { - //LB domain settings - id_col: 'USUBJID', - studyday_col: 'DY', - value_col: 'STRESN', - measure_col: 'TEST', - normal_col_low: null, - normal_col_high: 'STNRHI', - visit_col: null, - visitn_col: null, - - //DM domain settings - group_cols: null, - filters: null, - details: null, - - //EX domain settings - exposure_stdy_col: 'EXSTDY', - exposure_endy_col: 'EXENDY', - exposure_trt_col: 'EXTRT', - exposure_dose_col: 'EXDOSE', - exposure_dosu_col: 'EXDOSU', - - //analysis settings - analysisFlag: { - value_col: null, - values: [] - }, - baseline: { - value_col: null, //synced with studyday_col in syncsettings() - values: [0] - }, - calculate_palt: false, - paltFlag: { - value_col: null, - values: [] - }, - measure_values: { - ALT: 'Aminotransferase, alanine (ALT)', - AST: 'Aminotransferase, aspartate (AST)', - TB: 'Total Bilirubin', - ALP: 'Alkaline phosphatase (ALP)' - }, - add_measures: false, - x_options: 'all', - x_default: 'ALT', - y_options: ['TB'], - y_default: 'TB', - point_size_options: 'all', - point_size_default: 'Uniform', - cuts: { - TB: { - relative_baseline: 4.8, - relative_uln: 2 - }, - ALP: { - relative_baseline: 3.8, - relative_uln: 1 - }, - defaults: { - relative_baseline: 3.8, - relative_uln: 3 - } - }, - imputation_methods: { - ALT: 'data-driven', - AST: 'data-driven', - TB: 'data-driven', - ALP: 'data-driven' - }, - imputation_values: null, - display: 'relative_uln', //or "relative_baseline" - plot_max_values: true, - plot_day: null, //set in onLayout/initStudyDayControl - display_options: [ - { - label: 'Upper limit of normal adjusted (eDish)', - value: 'relative_uln' - }, - { label: 'Baseline adjusted (mDish)', value: 'relative_baseline' } - ], - measureBounds: [0.01, 0.99], - populationProfileURL: null, - participantProfileURL: null, - r_ratio_filter: true, - r_ratio: [0, null], - visit_window: 30, - title: 'Hepatic Safety Explorer', - downloadLink: true, - filters_multiselect: true, - warningText: - "This graphic has been thoroughly tested, but is not validated. Any clinical recommendations based on this tool should be confirmed using your organization's standard operating procedures.", - - //all values set in onLayout/quadrants/*.js - quadrants: [ - { - label: "Possible Hy's Law Range", - position: 'upper-right', - dataValue: 'xHigh:yHigh', - count: null, - total: null, - percent: null - }, - { - label: 'Hyperbilirubinemia', - position: 'upper-left', - dataValue: 'xNormal:yHigh', - count: null, - total: null, - percent: null - }, - { - label: "Temple's Corollary", - position: 'lower-right', - dataValue: 'xHigh:yNormal', - count: null, - total: null, - percent: null - }, - { - label: 'Normal Range', - position: 'lower-left', - dataValue: 'xNormal:yNormal', - count: null, - total: null, - percent: null - } - ], - - //Standard webcharts settings - x: { - column: null, //set in onPreprocess/updateAxisSettings - label: null, // set in onPreprocess/updateAxisSettings, - type: 'linear', - behavior: 'raw', - format: '.2f' - //domain: [0, null] - }, - y: { - column: null, // set in onPreprocess/updateAxisSettings, - label: null, // set in onPreprocess/updateAxisSettings, - type: 'linear', - behavior: 'raw', - format: '.2f' - //domain: [0, null] - }, - marks: [ - { - per: [], // set in syncSettings() - type: 'circle', - summarizeY: 'mean', - summarizeX: 'mean', - attributes: { 'fill-opacity': 0 } - } - ], - gridlines: 'xy', - color_by: null, //set in syncSettings - max_width: 600, - aspect: 1, - legend: { location: 'top' }, - margin: { right: 25, top: 25, bottom: 75 } - }; - } - - //Replicate settings in multiple places in the settings object - function syncSettings(settings$$1) { - var defaults = settings(); - settings$$1.marks[0].per[0] = settings$$1.id_col; - - //set grouping config - if (typeof settings$$1.group_cols == 'string') { - settings$$1.group_cols = [ - { value_col: settings$$1.group_cols, label: settings$$1.group_cols } - ]; - } - - if (!(settings$$1.group_cols instanceof Array && settings$$1.group_cols.length)) { - settings$$1.group_cols = [{ value_col: 'NONE', label: 'None' }]; - } else { - settings$$1.group_cols = settings$$1.group_cols.map(function (group) { - return { - value_col: group.value_col || group, - label: group.label || group.value_col || group - }; - }); - - var hasNone = - settings$$1.group_cols - .map(function (m) { - return m.value_col; - }) - .indexOf('NONE') > -1; - if (!hasNone) { - settings$$1.group_cols.unshift({ value_col: 'NONE', label: 'None' }); - } - } - - if (settings$$1.group_cols.length > 1) { - settings$$1.color_by = settings$$1.group_cols[1].value_col - ? settings$$1.group_cols[1].value_col - : settings$$1.group_cols[1]; - } else { - settings$$1.color_by = 'NONE'; - } - - //make sure filters is an Array - if (!(settings$$1.filters instanceof Array)) { - settings$$1.filters = - typeof settings$$1.filters == 'string' ? [settings$$1.filters] : []; - } - - //Define default details. - var defaultDetails = [{ value_col: settings$$1.id_col, label: 'Subject Identifier' }]; - if (settings$$1.filters) { - settings$$1.filters.forEach(function (filter) { - var obj = { - value_col: filter.value_col ? filter.value_col : filter, - label: filter.label - ? filter.label - : filter.value_col - ? filter.value_col - : filter - }; - - if ( - defaultDetails.find(function (f) { - return f.value_col == obj.value_col; - }) == undefined - ) { - defaultDetails.push(obj); - } - }); - } - - if (settings$$1.group_cols) { - settings$$1.group_cols - .filter(function (f) { - return f.value_col != 'NONE'; - }) - .forEach(function (group) { - var obj = { - value_col: group.value_col ? group.value_col : filter, - label: group.label - ? group.label - : group.value_col - ? group.value_col - : filter - }; - if ( - defaultDetails.find(function (f) { - return f.value_col == obj.value_col; - }) == undefined - ) { - defaultDetails.push(obj); - } - }); - } - - //parse details to array if needed - if (!(settings$$1.details instanceof Array)) { - settings$$1.details = - typeof settings$$1.details == 'string' ? [settings$$1.details] : []; - } - - //If [settings.details] is not specified: - if (!settings$$1.details) settings$$1.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$$1.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$$1.details = defaultDetails; - } - - // If settings.analysisFlag is null - if (!settings$$1.analysisFlag) settings$$1.analysisFlag = { value_col: null, values: [] }; - if (!settings$$1.analysisFlag.value_col) settings$$1.analysisFlag.value_col = null; - if (!(settings$$1.analysisFlag.values instanceof Array)) { - settings$$1.analysisFlag.values = - typeof settings$$1.analysisFlag.values == 'string' - ? [settings$$1.analysisFlag.values] - : []; - } - - // If settings.paltFlag is null - if (!settings$$1.paltFlag) settings$$1.paltFlag = { value_col: null, values: [] }; - if (!settings$$1.paltFlag.value_col) settings$$1.paltFlag.value_col = null; - if (!(settings$$1.paltFlag.values instanceof Array)) { - settings$$1.paltFlag.values = - typeof settings$$1.paltFlag.values == 'string' ? [settings$$1.paltFlag.values] : []; - } - - //if it is null, set settings.baseline.value_col to settings.studyday_col. - if (!settings$$1.baseline) settings$$1.baseline = { value_col: null, values: [] }; - if (!settings$$1.baseline.value_col) - settings$$1.baseline.value_col = settings$$1.studyday_col; - if (!(settings$$1.baseline.values instanceof Array)) { - settings$$1.baseline.values = - typeof settings$$1.baseline.values == 'string' ? [settings$$1.baseline.values] : []; - } - - //merge in default measure_values if user hasn't specified changes - Object.keys(defaults.measure_values).forEach(function (val) { - if (!settings$$1.measure_values.hasOwnProperty(val)) - settings$$1.measure_values[val] = defaults.measure_values[val]; - }); - - //check for 'all' in x_, y_ and point_size_options, but keep track if all options are used for later - var allMeasures = Object.keys(settings$$1.measure_values); - settings$$1.x_options_all = settings$$1.x_options == 'all'; - if (settings$$1.x_options == 'all') settings$$1.x_options = allMeasures; - settings$$1.y_options_all = settings$$1.y_options == 'all'; - if (settings$$1.y_options == 'all') settings$$1.y_options = allMeasures; - settings$$1.point_size_options_all = settings$$1.point_size_options == 'all'; - if (settings$$1.point_size_options == 'all') settings$$1.point_size_options = allMeasures; - - //parse x_ and y_options to array if needed - if (!(settings$$1.x_options instanceof Array)) { - settings$$1.x_options = - typeof settings$$1.x_options == 'string' ? [settings$$1.x_options] : []; - } - - if (!(settings$$1.y_options instanceof Array)) { - settings$$1.y_options = - typeof settings$$1.y_options == 'string' ? [settings$$1.y_options] : []; - } - - //set starting values for axis and point size settings. - settings$$1.point_size = - settings$$1.point_size_options.indexOf(settings$$1.point_size_default) > -1 - ? settings$$1.point_size_default - : settings$$1.point_size_default == 'rRatio' - ? 'rRatio' - : 'Uniform'; - settings$$1.x.column = - settings$$1.x_options.indexOf(settings$$1.x_default) > -1 - ? settings$$1.x_default - : settings$$1.x_options[0]; - settings$$1.y.column = - settings$$1.y_options.indexOf(settings$$1.y_default) > -1 - ? settings$$1.y_default - : settings$$1.y_options[0]; - - // track initial Cutpoint (lets us detect when cutpoint should change) - settings$$1.cuts.x = settings$$1.x.column; - settings$$1.cuts.y = settings$$1.y.column; - settings$$1.cuts.display = settings$$1.display; - - // Confirm detault cuts are set - settings$$1.cuts.defaults = settings$$1.cuts.defaults || defaults.cuts.defaults; - settings$$1.cuts.defaults.relative_uln = - settings$$1.cuts.defaults.relative_uln || defaults.cuts.defaults.relative_uln; - settings$$1.cuts.defaults.relative_baseline = - settings$$1.cuts.defaults.relative_baseline || defaults.cuts.defaults.relative_baseline; - - // keep default cuts if user hasn't provided an alternative - var cutMeasures = Object.keys(settings$$1.cuts); - Object.keys(defaults.cuts).forEach(function (m) { - if (cutMeasures.indexOf(m) == -1) { - settings$$1.cuts[m] = defaults.cuts[m]; - } - }); - - return settings$$1; - } - - function controlInputs() { - return [ - { - type: 'number', - label: 'R Ratio Range', - description: 'Filter points based on R ratio [(ALT/ULN) / (ALP/ULN)]', - option: 'r_ratio[0]' - }, - { - type: 'number', - label: null, //combined with r_ratio[0] control in formatRRatioControl() - description: null, - option: 'r_ratio[1]' - }, - { - type: 'dropdown', - label: 'Group', - description: 'Grouping variable', - options: ['color_by'], - start: null, // set in syncControlInputs() - values: ['NONE'], // set in syncControlInputs() - require: true - }, - { - type: 'dropdown', - label: 'Display Type', - description: 'Relative or absolute axes', - options: ['displayLabel'], - start: null, // set in syncControlInputs() - values: null, // set in syncControlInputs() - require: true - }, - { - type: 'radio', - option: 'plot_max_values', - label: 'Plot Style', - values: [true, false], - relabels: ['Max Values', 'By Study Day'] - }, - { - type: 'number', - label: 'Study Day', - description: null, //set in onDraw/updateStudyDaySlider - option: 'plot_day' - }, - { - type: 'dropdown', - label: 'X-axis Measure', - description: null, // set in syncControlInputs() - option: 'x.column', - start: null, // set in syncControlInputs() - values: null, //set in syncControlInptus() - require: true - }, - { - type: 'number', - label: null, // set in syncControlInputs - description: 'X-axis Reference Line', - option: null // set in syncControlInputs - }, - { - type: 'dropdown', - label: 'Y-axis Measure', - description: null, // set in syncControlInputs() - option: 'y.column', - start: null, // set in syncControlInputs() - values: null, //set in syncControlInptus() - require: true - }, - { - type: 'number', - label: null, // set in syncControlInputs - description: 'Y-axis Reference Line', - option: null // set in syncControlInputs - }, - { - type: 'dropdown', - label: 'Point Size', - description: 'Parameter to set point radius', - options: ['point_size'], - start: null, // set in syncControlInputs() - values: ['Uniform', 'rRatio'], - require: true - }, - { - type: 'dropdown', - label: 'Axis Type', - description: 'Linear or Log Axes', - options: ['x.type', 'y.type'], - start: null, // set in syncControlInputs() - values: ['linear', 'log'], - require: true - }, - { - type: 'number', - label: 'Highlight Points Based on Timing', - description: 'Fill points with max values less than X days apart', - option: 'visit_window' - } - ]; - } - - //Map values from settings to control inputs - function syncControlInputs(controlInputs, settings) { - //////////////////////// - // Group control - /////////////////////// - - var groupControl = controlInputs.find(function (controlInput) { - return controlInput.label === 'Group'; - }); - - //sync start value - groupControl.start = settings.color_by; //sync start value - - //sync values - settings.group_cols - .filter(function (group) { - return group.value_col !== 'NONE'; - }) - .forEach(function (group) { - groupControl.values.push(group.value_col); - }); - - //drop the group control if NONE is the only option - if (settings.group_cols.length == 1) - controlInputs = controlInputs.filter(function (controlInput) { - return controlInput.label != 'Group'; - }); - - ////////////////////////// - // x-axis measure control - ////////////////////////// - - // drop the control if there's only one option - if (settings.x_options.length === 1) - controlInputs = controlInputs.filter(function (controlInput) { - return controlInput.option !== 'x.column'; - }); - else { - //otherwise sync the properties - var xAxisMeasureControl = controlInputs.find(function (controlInput) { - return controlInput.option === 'x.column'; - }); - - //xAxisMeasureControl.description = settings.x_options.join(', '); - xAxisMeasureControl.start = settings.x.column; - xAxisMeasureControl.values = settings.x_options; - } - - ////////////////////////////////// - // x-axis reference line control - ////////////////////////////////// - - var xRefControl = controlInputs.find(function (controlInput) { - return controlInput.description === 'X-axis Reference Line'; - }); - xRefControl.label = settings.x_options[0] + ' Cutpoint'; - xRefControl.option = 'settings.cuts.' + [settings.x.column] + '.' + [settings.display]; - - //////////////////////////// - // y-axis measure control - //////////////////////////// - - // drop the control if there's only one option - if (settings.y_options.length === 1) - controlInputs = controlInputs.filter(function (controlInput) { - return controlInput.option !== 'y.column'; - }); - else { - //otherwise sync the properties - var yAxisMeasureControl = controlInputs.find(function (controlInput) { - return controlInput.option === 'y.column'; - }); - // yAxisMeasureControl.description = settings.y_options.join(', '); - yAxisMeasureControl.start = settings.y.column; - yAxisMeasureControl.values = settings.y_options; - } - - ////////////////////////////////// - // y-axis reference line control - ////////////////////////////////// - - var yRefControl = controlInputs.find(function (controlInput) { - return controlInput.description === 'Y-axis Reference Line'; - }); - yRefControl.label = settings.y_options[0] + ' Cutpoint'; - - yRefControl.option = 'settings.cuts.' + [settings.y.column] + '.' + [settings.display]; - - ////////////////////////////////// - // R ratio filter control - ////////////////////////////////// - - //drop the R Ratio control if r_ratio_filter is false - if (!settings.r_ratio_filter) { - controlInputs = controlInputs.filter(function (controlInput) { - return ['r_ratio[0]', 'r_ratio[1]'].indexOf(controlInput.option) == -1; - }); - } - - ////////////////////////////////// - // Point size control - ////////////////////////////////// - - var pointSizeControl = controlInputs.find(function (ci) { - return ci.label === 'Point Size'; - }); - - pointSizeControl.start = settings.point_size; - - settings.point_size_options.forEach(function (d) { - pointSizeControl.values.push(d); - }); - - //drop the pointSize control if NONE is the only option - if (settings.point_size_options.length == 0) - controlInputs = controlInputs.filter(function (controlInput) { - return controlInput.label != 'Point Size'; - }); - - ////////////////////////////////// - // Display control - ////////////////////////////////// - - controlInputs.find(function (controlInput) { - return controlInput.label === 'Display Type'; - }).values = settings.display_options.map(function (m) { - return m.label; - }); - - ////////////////////////////////// - // Add filters to inputs - ////////////////////////////////// - if (settings.filters && settings.filters.length > 0) { - var otherFilters = settings.filters.map(function (filter) { - filter = { - type: 'subsetter', - value_col: filter.value_col ? filter.value_col : filter, - label: filter.label - ? filter.label - : filter.value_col - ? filter.value_col - : filter, - multiple: settings.filters_multiselect - }; - return filter; - }); - return d3.merge([otherFilters, controlInputs]); - } else return controlInputs; - } - - var configuration = { - settings: settings, - syncSettings: syncSettings, - controlInputs: controlInputs, - syncControlInputs: syncControlInputs - }; - - function checkMeasureDetails() { - var chart = this; - var config = this.config; - var measures = d3 - .set( - this.raw_data.map(function (d) { - return d[config.measure_col]; - }) - ) - .values() - .sort(); - var specifiedMeasures = Object.keys(config.measure_values).map(function (e) { - return config.measure_values[e]; - }); - var missingMeasures = []; - Object.keys(config.measure_values).forEach(function (d) { - if (measures.indexOf(config.measure_values[d]) == -1) { - missingMeasures.push(config.measure_values[d]); - delete config.measure_values[d]; - } - }); - var nMeasuresRemoved = missingMeasures.length; - if (nMeasuresRemoved > 0) - console.warn( - 'The data are missing ' + - (nMeasuresRemoved === 1 ? 'this measure' : 'these measures') + - ': ' + - missingMeasures.join(', ') + - '.' - ); - - //automatically add Measures if requested - if (config.add_measures) { - measures.forEach(function (m, i) { - if (specifiedMeasures.indexOf(m) == -1) { - config.measure_values['m' + i] = m; - } - }); - } - - //check that x_options, y_options and size_options all have value keys/values in measure_values - var valid_options = Object.keys(config.measure_values); - var all_settings = ['x_options', 'y_options', 'point_size_options']; - all_settings.forEach(function (setting) { - // remove invalid options - config[setting].forEach(function (option) { - if (valid_options.indexOf(option) == -1) { - delete config[options][option]; - console.warn( - option + - " wasn't found in the measure_values index and has been removed from config." + - setting + - '. This may cause problems with the chart.' - ); - } - }); - - // update the control input settings - var controlLabel = - setting == 'x_options' - ? 'X-axis Measure' - : setting == 'y_options' - ? 'Y-axis Measure' - : 'Point Size'; - var input = chart.controls.config.inputs.find(function (ci) { - return ci.label == controlLabel; - }); - - if (input) { - //only update this if the input settings exist - axis inputs with only one value are deleted - // add options for controls requesting 'all' measures - if (config[setting + '_all']) { - var point_size_options = d3.merge([['Uniform', 'rRatio'], valid_options]); - config[setting] = - setting == 'point_size_options' ? point_size_options : valid_options; - input.values = config[setting]; - } - } - }); - //check that all measure_values have associated cuts - Object.keys(config.measure_values).forEach(function (m) { - // does a cut point for the measure exist? If not, create a placeholder. - if (!config.cuts.hasOwnProperty(m)) { - config.cuts[m] = {}; - } - - // does the cut have non-null baseline and ULN cuts associated, if not use the default values - config.cuts[m].relative_baseline = - config.cuts[m].relative_baseline || config.cuts.defaults.relative_baseline; - config.cuts[m].relative_uln = - config.cuts[m].relative_uln || config.cuts.defaults.relative_uln; - }); - } - - function iterateOverData() { - var _this = this; - - this.raw_data.forEach(function (d) { - d[_this.config.x.column] = null; // placeholder variable for x-axis - d[_this.config.y.column] = null; // placeholder variable for y-axis - d.NONE = 'All Participants'; // placeholder variable for non-grouped comparisons - - //Remove space characters from result variable. - if (typeof d[_this.config.value_col] == 'string') - d[_this.config.value_col] = d[_this.config.value_col].replace(/\s/g, ''); // remove space characters - }); - } - - function addRRatioFilter() { - if (this.config.r_ratio_filter) { - this.filters.push({ - col: 'rRatioFlag', - val: 'Y', - choices: ['Y', 'N'], - loose: undefined - }); - } - } - - function imputeColumn(data, measure_column, value_column, measure, llod, imputed_value, drop) { - //returns a data set with imputed values (or drops records) for records at or below a lower threshold for a given measure - //data = the data set for imputation - //measure_column = the column with the text measure names - //value_column = the column with the numeric values to be changed via imputation - //measure = the measure to be imputed - //llod = the lower limit of detection - values at or below the llod are imputed - //imputed_value = value for imputed records - //drop = boolean flag indicating whether values at or below the llod should be dropped (default = false) - - if (drop == undefined) drop = false; - if (drop) { - return data.filter(function (f) { - dropFlag = d[measure_column] == measure && +d[value_column] <= 0; - return !dropFlag; - }); - } else { - data.forEach(function (d) { - if ( - d[measure_column] == measure && - +d[value_column] < +llod && - d[value_column] >= 0 - ) { - d.impute_flag = true; - d[value_column + '_original'] = d[value_column]; - d[value_column] = imputed_value; - } - }); - - var impute_count = d3.sum( - data.filter(function (d) { - return d[measure_column] === measure; - }), - function (f) { - return f.impute_flag; - } - ); - - if (impute_count > 0) - console.warn( - '' + - impute_count + - ' value(s) less than ' + - llod + - ' were imputed to ' + - imputed_value + - ' for ' + - measure - ); - return data; - } - } - - function imputeData() { - var chart = this; - var config = this.config; - - Object.keys(config.measure_values).forEach(function (measureKey) { - var values = chart.imputed_data - .filter(function (f) { - return f[config.measure_col] == config.measure_values[measureKey]; - }) - .map(function (m) { - return +m[config.value_col]; - }) - .sort(function (a, b) { - return a - b; - }), - minValue = d3.min( - values.filter(function (f) { - return f > 0; - }) - ), - //minimum value > 0 - llod = null, - imputed_value = null, - drop = null; - - if (config.imputation_methods[measureKey] == 'data-driven') { - llod = minValue; - imputed_value = minValue / 2; - drop = false; - } else if (config.imputation_methods[measureKey] == 'user-defined') { - llod = config.imputation_values[measureKey]; - imputed_value = config.imputation_values[measureKey] / 2; - drop = false; - } else if (config.imputation_methods[measureKey] == 'drop') { - llod = null; - imputed_value = null; - drop = true; - } - chart.imputed_data = imputeColumn( - chart.imputed_data, - config.measure_col, - config.value_col, - config.measure_values[measureKey], - llod, - imputed_value, - drop - ); - - var total_imputed = d3.sum(chart.raw_data, function (f) { - return f.impute_flag ? 1 : 0; - }); - }); - } - - function dropRows() { - var chart = this; - var config = this.config; - this.dropped_rows = []; - - ///////////////////////// - // Remove invalid rows - ///////////////////////// - var numerics = ['value_col', 'studyday_col', 'normal_col_high']; - chart.imputed_data = chart.initial_data.filter(function (f) { - return true; - }); - numerics.forEach(function (setting) { - chart.imputed_data = chart.imputed_data.filter(function (d) { - //Remove non-numeric value_col - var numericCol = /^-?(\d*\.?\d+|\d+\.?\d*)(E-?\d+)?$/.test(d[config[setting]]); - if (!numericCol) { - d.dropReason = setting + ' Column ("' + config[setting] + '") is not numeric.'; - chart.dropped_rows.push(d); - } - return numericCol; - }); - }); - } - - function deriveVariables() { - var config = this.config; - - //filter the lab data to only the required measures - var included_measures = Object.keys(config.measure_values).map(function (e) { - return config.measure_values[e]; - }); - - var sub = this.imputed_data.filter(function (f) { - return included_measures.indexOf(f[config.measure_col]) > -1; - }); - - var missingBaseline = 0; - - //coerce numeric values to number - this.imputed_data = this.imputed_data.map(function (d) { - var numerics = ['value_col', 'studyday_col', 'normal_col_low', 'normal_col_high']; - numerics.forEach(function (col) { - d[config[col]] = parseFloat(d[config[col]]); - }); - return d; - }); - - //create an object mapping baseline values for id/measure pairs - var baseline_records = sub.filter(function (f) { - var current = - typeof f[config.baseline.value_col] == 'string' - ? f[config.baseline.value_col].trim() - : parseFloat(f[config.baseline.value_col]); - return config.baseline.values.indexOf(current) > -1; - }); - - var baseline_values = d3 - .nest() - .key(function (d) { - return d[config.id_col]; - }) - .key(function (d) { - return d[config.measure_col]; - }) - .rollup(function (d) { - return d[0][config.value_col]; - }) - .map(baseline_records); - - this.imputed_data = this.imputed_data.map(function (d) { - //standardize key variables - d.key_measure = false; - if (included_measures.indexOf(d[config.measure_col]) > -1) { - d.key_measure = true; - - //map the raw value to a variable called 'absolute' - d.absolute = d[config.value_col]; - - //get the value relative to the ULN (% of the upper limit of normal) for the measure - d.uln = d[config.normal_col_high]; - d.relative_uln = d[config.value_col] / d[config.normal_col_high]; - - //get value relative to baseline - if (baseline_values[d[config.id_col]]) { - if (baseline_values[d[config.id_col]][d[config.measure_col]]) { - d.baseline_absolute = - baseline_values[d[config.id_col]][d[config.measure_col]]; - d.relative_baseline = d.absolute / d.baseline_absolute; - } else { - missingBaseline = missingBaseline + 1; - d.baseline_absolute = null; - d.relative_baseline = null; - } - } else { - missingBaseline = missingBaseline + 1; - d.baseline_absolute = null; - d.relative_baseline = null; - } - } - return d; - }); - - if (config.debug & (missingBaseline > 0)) - console.warn( - 'No baseline value found for ' + missingBaseline + ' of ' + sub.length + ' records.' - ); - } - - function makeAnalysisFlag() { - var config = this.config; - this.imputed_data = this.imputed_data.map(function (d) { - var hasAnalysisSetting = - config.analysisFlag.value_col != null && config.analysisFlag.values.length > 0; - d.analysisFlag = hasAnalysisSetting - ? config.analysisFlag.values.indexOf(d[config.analysisFlag.value_col]) > -1 - : true; - return d; - }); - } - - function makePaltFlag() { - var config = this.config; - this.imputed_data = this.imputed_data.map(function (d) { - var hasPaltSetting = - config.paltFlag.value_col != null && config.paltFlag.values.length > 0; - d.paltFlag = hasPaltSetting - ? config.paltFlag.values.indexOf(d[config.paltFlag.value_col]) > -1 - : true; - return d; - }); - } - - function cleanData() { - var config = this.config; - - //drop rows with invalid data - this.imputedData = dropRows.call(this); - - this.imputed_data.forEach(function (d) { - d.impute_flag = false; - }); - - imputeData.call(this); - deriveVariables.call(this); - makeAnalysisFlag.call(this); - makePaltFlag.call(this); - } - - function initCustomEvents() { - var chart = this; - chart.participantsSelected = []; - chart.events.participantsSelected = new CustomEvent('participantsSelected'); - } - - function onInit() { - checkMeasureDetails.call(this); - iterateOverData.call(this); - addRRatioFilter.call(this); - cleanData.call(this); //clean visit-level data - imputation and variable derivations - initCustomEvents.call(this); - } - - function formatRRatioControl() { - var chart = this; - var config = this.config; - if (this.config.r_ratio_filter) { - var min_r_ratio = this.controls.wrap.selectAll('.control-group').filter(function (d) { - return d.option === 'r_ratio[0]'; - }); - var min_r_ratio_input = min_r_ratio.select('input'); - - var max_r_ratio = this.controls.wrap.selectAll('.control-group').filter(function (d) { - return d.option === 'r_ratio[1]'; - }); - var max_r_ratio_input = max_r_ratio.select('input'); - - min_r_ratio_input.attr('id', 'r_ratio_min'); - max_r_ratio_input.attr('id', 'r_ratio_max'); - - //move the max r ratio control next to the min control - min_r_ratio.append('span').text(' - '); - min_r_ratio.append(function () { - return max_r_ratio_input.node(); - }); - - max_r_ratio.remove(); - - //add a reset button - min_r_ratio - .append('button') - .style('padding', '0.2em 0.5em 0.2em 0.4em') - .style('margin-left', '0.5em') - .style('border-radius', '0.4em') - .text('Reset') - .on('click', function () { - config.r_ratio[0] = 0; - min_r_ratio.select('input#r_ratio_min').property('value', config.r_ratio[0]); - config.r_ratio[1] = config.max_r_ratio; - min_r_ratio.select('input#r_ratio_max').property('value', config.r_ratio[1]); - chart.draw(); - }); - } - } - - function updateSummaryTable() { - var chart = this; - var config = chart.config; - var quadrants = this.config.quadrants; - var rows = quadrants.table.rows; - var cells = quadrants.table.cells; - - function updateCells(d) { - var cellData = cells.map(function (cell) { - cell.value = d[cell.value_col]; - return cell; - }); - var row_cells = d3 - .select(this) - .selectAll('td') - .data(cellData, function (d) { - return d.value_col; - }); - - row_cells - .enter() - .append('td') - .style('text-align', function (d, i) { - return d.label != 'Quadrant' ? 'center' : null; - }) - .style('font-size', '0.9em') - .style('padding', '0 0.5em 0 0.5em'); - - row_cells.html(function (d) { - return d.value; - }); - } - - //make sure the table is visible - this.config.quadrants.table.wrap.style('display', null); - //update the content of each row - rows.data(quadrants, function (d) { - return d.label; - }); - rows.each(updateCells); - } - - function initSummaryTable() { - var chart = this; - var config = chart.config; - var quadrants = this.config.quadrants; - - quadrants.table = {}; - quadrants.table.wrap = this.wrap - .append('div') - .attr('class', 'quadrantTable') - .style('padding-top', '1em'); - quadrants.table.tab = quadrants.table.wrap - .append('table') - .style('border-collapse', 'collapse'); - - //table header - quadrants.table.cells = [ - { - value_col: 'label', - label: 'Quadrant' - }, - { - value_col: 'count', - label: '#' - }, - { - value_col: 'percent', - label: '%' - } - ]; - - if (config.populationProfileURL) { - quadrants.forEach(function (d) { - d.link = "🔗"; - }); - quadrants.table.cells.push({ - value_col: 'link', - label: 'Population Profile' - }); - } - quadrants.table.thead = quadrants.table.tab - .append('thead') - .style('border-top', '2px solid #999') - .style('border-bottom', '2px solid #999') - .append('tr') - .style('padding', '0.1em'); - - quadrants.table.thead - .selectAll('th') - .data(quadrants.table.cells) - .enter() - .append('th') - .html(function (d) { - return d.label; - }); - - //table contents - quadrants.table.tbody = quadrants.table.tab - .append('tbody') - .style('border-bottom', '2px solid #999'); - quadrants.table.rows = quadrants.table.tbody - .selectAll('tr') - .data(quadrants, function (d) { - return d.label; - }) - .enter() - .append('tr') - .style('padding', '0.1em'); - - //initial table update - updateSummaryTable.call(this); - } - - function init() { - var chart = this; - var config = chart.config; - var quadrants = this.config.quadrants; - - var x_input = chart.controls.wrap - .selectAll('div.control-group') - .filter(function (f) { - return f.description == 'X-axis Reference Line'; - }) - .select('input'); - - var y_input = chart.controls.wrap - .selectAll('div.control-group') - .filter(function (f) { - return f.description == 'Y-axis Reference Line'; - }) - .select('input'); - - /////////////////////////////////////////////////////////// - // set initial values - ////////////////////////////////////////////////////////// - x_input.node().value = config.cuts[config.x.column][config.display]; - y_input.node().value = config.cuts[config.y.column][config.display]; - - /////////////////////////////////////////////////////////// - // set control step to 0.1 - ////////////////////////////////////////////////////////// - x_input.attr('step', 0.1); - y_input.attr('step', 0.1); - - /////////////////////////////////////////////////////////// - // initialize the summary table - ////////////////////////////////////////////////////////// - initSummaryTable.call(chart); - } - - function layoutQuadrantLabels() { - var chart = this; - var config = chart.config; - var quadrants = this.config.quadrants; - - ////////////////////////////////////////////////////////// - //layout the quadrant labels - ///////////////////////////////////////////////////////// - - chart.quadrant_labels = {}; - chart.quadrant_labels.g = this.svg.append('g').attr('class', 'quadrant-labels'); - - chart.quadrant_labels.text = chart.quadrant_labels.g - .selectAll('text.quadrant-label') - .data(quadrants) - .enter() - .append('text') - .attr('class', function (d) { - return 'quadrant-label ' + d.position; - }) - .attr('dy', function (d) { - return d.position.search('lower') > -1 ? '-.2em' : '.5em'; - }) - .attr('dx', function (d) { - return d.position.search('right') > -1 ? '-.5em' : '.5em'; - }) - .attr('text-anchor', function (d) { - return d.position.search('right') > 0 ? 'end' : null; - }) - .attr('fill', '#bbb') - .text(function (d) { - return d.label; - }); - } - - function layoutCutLines() { - var chart = this; - var config = chart.config; - var quadrants = this.config.quadrants; - - ////////////////////////////////////////////////////////// - //layout the cut lines - ///////////////////////////////////////////////////////// - chart.cut_lines = {}; - chart.cut_lines.wrap = this.svg.append('g').attr('class', 'cut-lines'); - var wrap = chart.cut_lines.wrap; - - //slight hack to make life easier on drag - var cutLineData = [{ dimension: 'x' }, { dimension: 'y' }]; - - cutLineData.forEach(function (d) { - d.chart = chart; - }); - - chart.cut_lines.g = wrap - .selectAll('g.cut') - .data(cutLineData) - .enter() - .append('g') - .attr('class', function (d) { - return 'cut ' + d.dimension; - }); - - chart.cut_lines.lines = chart.cut_lines.g - .append('line') - .attr('class', 'cut-line') - .attr('stroke-dasharray', '5,5') - .attr('stroke', '#bbb'); - - chart.cut_lines.backing = chart.cut_lines.g - .append('line') - .attr('class', 'cut-line-backing') - .attr('stroke', 'transparent') - .attr('stroke-width', '10') - .attr('cursor', 'move'); - } - - function initQuadrants() { - init.call(this); - layoutCutLines.call(this); - layoutQuadrantLabels.call(this); - } - - function initRugs() { - //initialize a 'rug' on each axis to show the distribution for a participant on addPointMouseover - this.x_rug = this.svg.append('g').attr('class', 'rug x'); - this.y_rug = this.svg.append('g').attr('class', 'rug y'); - } - - function initVisitPath() { - //initialize a 'rug' on each axis to show the distribution for a participant on addPointMouseover - this.visitPath = this.svg.append('g').attr('class', 'visit-path'); - } - - function initParticipantDetails() { - //layout participant details section - this.participantDetails = {}; - this.participantDetails.wrap = this.wrap.append('div').attr('class', 'participantDetails'); - - this.participantDetails.header = this.participantDetails.wrap - .append('div') - .attr('class', 'participantHeader'); - - //layout spaghettiPlot - var splot = this.participantDetails.wrap.append('div').attr('class', 'spaghettiPlot'); - splot - .append('h3') - .attr('class', 'id') - .html('Standardized Lab Values by Study Day') - .style('border-top', '2px solid black') - .style('border-bottom', '2px solid black') - .style('padding', '.2em'); - - splot.append('div').attr('class', 'chart'); - - //layout rRatio plot - var rrplot = this.participantDetails.wrap.append('div').attr('class', 'rrPlot'); - rrplot - .append('h3') - .attr('class', 'id') - .html('R Ratio by Study Day') - .style('border-top', '2px solid black') - .style('border-bottom', '2px solid black') - .style('padding', '.2em'); - - rrplot.append('div').attr('class', 'chart'); - - //layout measure table - var mtable = this.participantDetails.wrap.append('div').attr('class', 'measureTable'); - mtable - .append('h3') - .attr('class', 'id') - .html('Raw Lab Values Summary Table') - .style('border-top', '2px solid black') - .style('border-bottom', '2px solid black') - .style('padding', '.2em'); - - //initialize the measureTable - var settings = { - cols: ['key', 'n', 'min', 'median', 'max', 'spark'], - headers: ['Measure', 'N', 'Min', 'Median', 'Max', ''], - searchable: false, - sortable: false, - pagination: false, - exportable: false, - applyCSS: true - }; - this.measureTable = webcharts.createTable( - this.element + ' .participantDetails .measureTable', - settings - ); - this.measureTable.init([]); - - //hide the section until needed - this.participantDetails.wrap.selectAll('*').style('display', 'none'); - } - - function initResetButton() { - var chart = this; - - this.controls.reset = {}; - var reset = this.controls.reset; - reset.wrap = this.controls.wrap.append('div').attr('class', 'control-group'); - reset.label = reset.wrap - .append('span') - .attr('class', 'wc-control-label') - .text('Reset Chart'); - reset.button = reset.wrap - .append('button') - .text('Reset') - .on('click', function () { - var initial_container = chart.element; - var initial_settings = chart.initial_settings; - var initial_data = chart.initial_data; - chart.emptyChartWarning.remove(); - - chart.destroy(); - chart = null; - - var newChart = hepexplorer(initial_container, initial_settings); - newChart.init(initial_data); - }); - } - - function initDisplayControl() { - var chart = this; - var config = this.config; - var displayControlWrap = this.controls.wrap.selectAll('div').filter(function (controlInput) { - return controlInput.label === 'Display Type'; - }); - - var displayControl = displayControlWrap.select('select'); - - //set the start value - var start_value = config.display_options.find(function (f) { - return f.value == config.display; - }).label; - displayControl.selectAll('option').attr('selected', function (d) { - return d == start_value ? 'selected' : null; - }); - - //annotation of baseline visit (only visible when mDish is selected) - displayControlWrap - .append('span') - .attr('class', 'displayControlAnnotation span-description') - .style('color', 'blue') - .text( - 'Note: Baseline defined as ' + - chart.config.baseline.value_col + - ' = ' + - chart.config.baseline.values.join(',') - ) - .style('display', config.display == 'relative_baseline' ? null : 'none'); - - displayControl.on('change', function (d) { - var currentLabel = this.value; - var currentValue = config.display_options.find(function (f) { - return f.label == currentLabel; - }).value; - config.display = currentValue; - - if (currentValue == 'relative_baseline') { - displayControlWrap.select('span.displayControlAnnotation').style('display', null); - } else { - displayControlWrap.select('span.displayControlAnnotation').style('display', 'none'); - } - - config.cuts.display_change = true; - - chart.draw(); - }); - } - - function layoutPanels() { - this.wrap.style('display', 'inline-block').style('width', '75%'); - - this.controls.wrap - .style('display', 'inline-block') - .style('width', '25%') - .style('vertical-align', 'top'); - - this.controls.wrap.selectAll('div.control-group').style('display', 'block'); - this.controls.wrap - .selectAll('div.control-group') - .select('select') - .style('width', '200px'); - } - - function initTitle() { - this.titleDiv = this.controls.wrap - .insert('div', '*') - .attr('class', 'title') - .style('margin-right', '1em') - .style('margin-bottom', '1em'); - - this.titleDiv - .append('span') - .text(this.config.title) - .style('font-size', '1.5em') - .style('font-weight', 'strong') - .style('display', 'block'); - } - - function add(messageText, type, label, messages, callback) { - var messageObj = { - id: messages.list.length + 1, - type: type, - message: messageText, - label: label, - hidden: false, - callback: callback - }; - messages.list.push(messageObj); - messages.update(messages); - } - - function remove(id, label, messages) { - // hide the the message(s) by id or label - if (id) { - var matches = messages.list.filter(function (f) { - return +f.id == +id; - }); - } else if (label.length) { - var matches = messages.list.filter(function (f) { - return label == 'all' ? true : f.label == label; - }); - } - matches.forEach(function (d) { - d.hidden = true; - }); - messages.update(messages); - } - - function update(messages) { - function jsUcfirst(string) { - return string.charAt(0).toUpperCase() + string.slice(1); - } - - var visibleMessages = messages.list.filter(function (f) { - return f.hidden == false; - }); - - //update title - messages.header.title.text('Messages (' + visibleMessages.length + ')'); - - // - var messageDivs = messages.wrap.selectAll('div.message').data(visibleMessages, function (d) { - return d.id; - }); - - var newMessages = messageDivs - .enter() - .append('div') - .attr('class', function (d) { - return d.type + ' message ' + d.label; - }) - .html(function (d) { - var messageText = '' + jsUcfirst(d.type) + ': ' + d.message; - return messageText.split('.')[0] + '.'; - }) - .style('border-radius', '.5em') - .style('margin-right', '1em') - .style('margin-bottom', '0.5em') - .style('padding', '0.2em') - .style('font-size', '0.9em'); - - newMessages - .append('div.expand') - .html('•••') - .style('background', 'white') - .style('display', 'inline-block') - .style('border', '1px solid #999') - .style('padding', '0 0.2em') - .style('margin-left', '0.3em') - .style('font-size', '0.4em') - .style('border-radius', '0.6em') - .style('cursor', 'pointer') - .on('click', function (d) { - d3.select(this.parentNode) - .html(function (d) { - return '' + jsUcfirst(d.type) + ': ' + d.message; - }) - .each(function (d) { - if (d.callback) { - d.callback.call(this.parentNode); - } - }); - }); - - messageDivs.each(function (d) { - var type = d.type; - var thisMessage = d3.select(this); - if (type == 'caution') { - thisMessage - .style('border', '1px solid #faebcc') - .style('color', '#8a6d3b') - .style('background-color', '#fcf8e3'); - } else if (type == 'warning') { - thisMessage - .style('border', '1px solid #ebccd1') - .style('color', '#a94442') - .style('background-color', '#f2dede'); - } else { - thisMessage - .style('border', '1px solid #999') - .style('color', '#999') - .style('background-color', null); - } - - if (d.callback) { - d.callback.call(this); - } - }); - - messageDivs.exit().remove(); - } - - function init$1() { - var chart = this; - this.messages = { - add: add, - remove: remove, - update: update - }; - // this.messages.add = addMessage; - // this.messages.remove = removeMessage; - this.messages.list = []; - this.messages.wrap = this.controls.wrap.insert('div', '*').style('margin', '0 1em 1em 0'); - this.messages.header = this.messages.wrap - .append('div') - .style('border-top', '1px solid black') - .style('border-bottom', '1px solid black') - .style('font-weight', 'strong') - .style('margin', '0 1em 1em 0'); - - this.messages.header.title = this.messages.header - .append('div') - .attr('class', 'title') - .style('display', 'inline-block') - .text('Messages (0)'); - - this.messages.header.clear = this.messages.header - .append('div') - .text('Clear') - .style('font-size', '0.8em') - .style('vertical-align', 'center') - .style('display', 'inline-block') - .style('float', 'right') - .style('color', 'blue') - .style('cursor', 'pointer') - .style('text-decoration', 'underline') - .on('click', function () { - chart.messages.remove(null, 'all', chart.messages); - }); - } - - function initCustomWarning() { - if (this.config.warningText) { - this.messages.add( - this.config.warningText, - 'caution', - 'validationCaution', - this.messages - ); - } - } - - function downloadCSV(data, cols, file) { - var CSVarray = []; - - //add headers to CSV array - var cols = cols ? cols : Object.keys(data[0]); - var headers = cols.map(function (header) { - return '"' + header.replace(/"/g, '""') + '"'; - }); - CSVarray.push(headers); - //add rows to CSV array - data.forEach(function (d, i) { - var row = cols.map(function (col) { - var value = d[col]; - - if (typeof value === 'string') value = value.replace(/"/g, '""'); - - return '"' + value + '"'; - }); - - CSVarray.push(row); - }); - - //transform blob array into a blob of characters - var blob = new Blob([CSVarray.join('\n')], { - type: 'text/csv;charset=utf-8;' - }); - var fileCore = file ? file : 'eDish'; - var fileName = fileCore + '_' + d3.time.format('%Y-%m-%dT%H-%M-%S')(new Date()) + '.csv'; - var link = d3.select(this); - - 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 initDroppedRowsWarning() { - var chart = this; - if (this.dropped_rows.length > 0) { - var warningText = - this.dropped_rows.length + - ' rows were removed. This is probably because of non-numeric or missing data provided for key variables. Click here to download a csv with a brief explanation of why each row was removed.'; - - this.messages.add(warningText, 'caution', 'droppedRows', this.messages, function () { - //custom callback to activate the droppedRows download - d3.select(this) - .select('a.rowDownload') - .style('color', 'blue') - .style('text-decoration', 'underline') - .style('cursor', 'pointer') - .datum(chart.dropped_rows) - .on('click', function (d) { - var systemVars = d3.merge([ - ['dropReason', 'NONE'], - Object.keys(chart.config.measure_values) - ]); - var cols = d3.merge([ - ['dropReason'], - Object.keys(d[0]).filter(function (f) { - return systemVars.indexOf(f) == -1; - }) - ]); - downloadCSV.call(this, d, cols, 'eDishDroppedRows'); - }); - }); - } - } - - function initControlLabels() { - var config = this.config; - - //Add settings label - var first_setting = this.controls.wrap - .selectAll('div.control-group') - .filter(function (f) { - return f.type != 'subsetter'; - }) - .filter(function (f) { - return f.option != 'r_ratio[0]'; - }) - .filter(function (f, i) { - return i == 0; - }) - .attr('class', 'first-setting'); - - this.controls.setting_header = this.controls.wrap - .insert('div', '.first-setting') - .attr('class', 'subtitle') - .style('border-top', '1px solid black') - .style('border-bottom', '1px solid black') - .style('margin-right', '1em') - .style('margin-bottom', '1em'); - - this.controls.setting_header - .append('span') - .text('Settings') - .style('font-weight', 'strong') - .style('display', 'block'); - - //Add filter label if at least 1 filter exists - if (config.r_ratio_filter || config.filters.length > 0) { - //insert a header before the first filter - var control_wraps = this.controls.wrap - .selectAll('div') - .filter(function (controlInput) { - return ( - controlInput.label === 'R Ratio Range' || controlInput.type === 'subsetter' - ); - }) - .classed('subsetter', true); - - this.controls.filter_header = this.controls.wrap - .insert('div', 'div.subsetter') - .attr('class', 'subtitle') - .style('border-top', '1px solid black') - .style('border-bottom', '1px solid black') - .style('margin-right', '1em') - .style('margin-bottom', '1em'); - this.controls.filter_header - .append('span') - .text('Filters') - .style('font-weight', 'strong') - .style('display', 'block'); - var population = d3 - .set( - this.initial_data.map(function (m) { - return m[config.id_col]; - }) - ) - .values().length; - this.controls.filter_header - .append('span') - .attr('class', 'popCount') - .html( - '' + - population + - ' of ' + - population + - ' participants shown.' - ) - .style('font-size', '0.8em'); - - this.controls.filter_numerator = this.controls.filter_header - .select('span.popCount') - .select('span.numerator'); - this.controls.filter_denominator = this.controls.filter_header - .select('span.popCount') - .select('span.denominator'); - } - } - - function addFootnote() { - this.footnote = this.wrap - .append('div') - .attr('class', 'footnote') - .text('Use controls to update chart or click a point to see participant details.') - .style('font-size', '0.7em') - .style('padding-top', '0.1em'); - this.footnote.timing = this.footnote.append('p'); - } - - function addDownloadButton() { - var chart = this; - var config = this.config; - if (config.downloadLink) { - this.titleDiv - .select('span') - .append('a') - .attr('class', 'downloadRaw') - .html('↓ Raw Data') - .attr('title', 'Download Raw Data') - .style('font-size', '.5em') - .style('margin-left', '1em') - .style('border', '1px solid black') - .style('border-radius', '2px') - .style('padding', '2px 4px') - .style('text-align', 'center') - .style('display', 'inline-block') - .style('cursor', 'pointer') - .style('font-weight', 'bold') - .datum(chart.initial_data) - .on('click', function (d) { - var systemVars = [ - 'dropReason', - 'NONE', - 'ALT', - 'TB', - 'impute_flag', - 'key_measure', - 'analysisFlag' - ]; - var cols = Object.keys(d[0]).filter(function (f) { - return systemVars.indexOf(f) == -1; - }); - downloadCSV.call(this, d, cols, 'eDishRawData'); - }); - } - } - - function initEmptyChartWarning() { - this.emptyChartWarning = d3 - .select(this.element) - .append('span') - .text('No data selected. Try updating your settings or resetting the chart. ') - .style('display', 'none') - .style('color', '#a94442') - .style('background-color', '#f2dede') - .style('border', '1px solid #ebccd1') - .style('padding', '0.5em') - .style('margin', '0 2% 12px 2%') - .style('border-radius', '0.2em'); - } - - function relabelMeasureControls() { - var chart = this; - var config = this.config; - var controlLabels = ['X-axis Measure', 'Y-axis Measure', 'Point Size']; - var controlWraps = chart.controls.wrap.selectAll('div').filter(function (controlInput) { - return controlLabels.indexOf(controlInput.label) > -1; - }); - var controls = controlWraps.select('select'); - var options = controls.selectAll('option'); - var allKeys = Object.keys(config.measure_values); - options - .text(function (d) { - return allKeys.indexOf(d) > -1 ? config.measure_values[d] : d; - }) - .property('value', function (d) { - return d; - }); - } - - function stopAnimation() { - var chart = this; - chart.svg - .transition() - .duration(0) - .each('end', function () { - chart.controls.studyDayPlayButton.datum({ state: 'play' }); - chart.controls.studyDayPlayButton.html('►'); - chart.draw(); - }); - } - - function customizePlotStyleToggle() { - var chart = this; - this.controls.wrap - .selectAll('.control-group') - .filter(function (d) { - return d.option === 'plot_max_values'; - }) - .selectAll('input') - .on('change', function (d) { - chart.config.plot_max_values = d; - stopAnimation.call(chart); - }); - } - - function startAnimation() { - var chart = this; - var config = this.config; - - // calculate animation duration - var day_count = chart.controls.studyDayRange[1] - config.plot_day; - var duration = day_count < 100 ? day_count * 100 : 30000; - var day_duration = duration / day_count; - - var base_size = config.marks[0].radius || config.flex_point_size; - var small_size = base_size / 2; - - function reposition(point) { - point - .transition() - .duration(day_duration) - .attr('cx', function (d) { - return chart.x(d[config.x.column]); - }) - .attr('cy', function (d) { - return chart.y(d[config.y.column]); - }) - .attr('r', function (d) { - if (d.outOfRange) { - return small_size; - } else if (config.point_size == 'Uniform') { - return base_size; - } else { - return chart.sizeScale(d[config.point_size]); - } - }) - .attr('fill-opacity', function (d) { - return config.plot_day < d.day_range[0] ? 0 : 0.5; - }); - } - - function updateDatum(d, currentDay) { - // adds temporary x, y and size (if any) measures for the current day to the core datum (instead of under d.value) - // these get removed when the transition ends and chart.draw() is called. - var measures = [config.x.column, config.y.column]; - if (config.point_size != 'Uniform') { - measures = d3.merge([measures, [config.point_size]]); - } - - var raw = d.values.raw[0]; - d.outOfRange = false; - d.day_range = raw.day_range; - d.moved = false; - measures.forEach(function (m) { - var vals = raw[m + '_raw']; - // capture the previous point position - d[m + '_prev'] = d[m]; - - // Did currentDay occur while participant was enrolled? - if (vals && vals.length) { - // && [config.x.column, config.y.column].includes(m)) { // out-of-range should be calculated study day of with x- and y-axis measures - var first = vals[0]; - var last = vals[vals.length - 1]; - var before = currentDay < first.day; - var after = currentDay > last.day; - d.outOfRange = d.outOfRange || before || after; - } - - //Get the most recent data point (or the first point if participant isn't enrolled yet) - var getLastMeasureIndex = d3.bisector(function (d) { - return d.day; - }).right; - var lastMeasureIndexPlusOne = vals ? getLastMeasureIndex(vals, currentDay) : 0; - var lastMeasureIndex = lastMeasureIndexPlusOne - 1; - - d[m] = - lastMeasureIndex >= 0 - ? vals[lastMeasureIndex]['value'] - : vals && vals.length - ? vals[0].value - : null; - - d[m + '_length'] = d[m] - d[m + '_prev']; - if (d[m + '_length']) { - d.moved = true; - } - }); - return d; - } - - function showDay(currentDay) { - //update the controls - config.plot_day = Math.floor(currentDay); - chart.controls.studyDayInput.node().value = config.plot_day; - - //update the label - chart.controls.studyDayControlWrap - .select('span.wc-control-label') - .html('Showing data from: Day ' + config.plot_day + '') - .select('strong') - .style('color', 'blue'); - - //reposition the points - var marks = chart.marks[0]; - - var groups = marks.groups.datum(function (d) { - return updateDatum(d, config.plot_day); - }); - - var points = groups.select('circle').each(function (d) { - if (d.moved) d3.select(this).call(reposition); - }); - - //draw trails - var tails = groups - .filter(function (d) { - return d.moved; - }) - .insert('line', ':first-child') - //static attributes - .attr('x1', function (d) { - return chart.x(d[config.x.column + '_prev']); - }) - .attr('y1', function (d) { - return chart.y(d[config.y.column + '_prev']); - }) - .attr('stroke', function (d) { - return chart.colorScale(d.values.raw[0][config.color_by]); - }) - //.attr('stroke', '#999') - - //transitional attributes - .attr('x2', function (d) { - return chart.x(d[config.x.column + '_prev']); - }) - .attr('y2', function (d) { - return chart.y(d[config.y.column + '_prev']); - }) - .attr('stroke-width', base_size); - tails.each(function (d) { - var path = d3.select(this); - var transition1 = path - .transition() - .duration(day_duration) - .ease('linear') - .attr('x2', function (d) { - return chart.x(d[config.x.column]); - }) - .attr('y2', function (d) { - return chart.y(d[config.y.column]); - }); - var transition2 = transition1 - .transition() - .duration(day_duration * 10) - .attr('stroke-width', '0px'); - }); - } - - function tweenStudyDay() { - var min = config.plot_day; - var max = chart.controls.studyDayRange[1]; - var studyday = d3.interpolateNumber(min, max); - - return function (t) { - showDay(studyday(t)); - }; - } - - //draw the chart to clear details view - chart.draw(); - - //hide quadrant info during startAnimation - chart.config.quadrants.table.wrap.style('display', 'none'); - chart.quadrant_labels.g.attr('display', 'none'); - - //show the stop button - chart.controls.studyDayPlayButton.datum({ state: 'stop' }); - chart.controls.studyDayPlayButton.html('■'); - - // Initialize the Transition - chart.myTransition = chart.svg - .transition() - .duration(duration) - .ease('linear') - .tween('studyday', tweenStudyDay) - .each('end', function () { - chart.draw(); - }); - } - - // ► = play symbol - // ■ = stop symbol - // ↺ = restart symbol - - function initPlayButton() { - var chart = this; - var config = this.config; - chart.controls.studyDayPlayButton = chart.controls.studyDayControlWrap - .append('button') - .datum({ state: 'play' }) - .html('►') //play symbol - .style('padding', '0.2em 0.5em 0.2em 0.5em') - .style('margin-left', '0.5em') - .style('border-radius', '0.4em') - //.style('display', 'none') - .on('click', function (d) { - var button = d3.select(this); - if (d.state === 'play') { - startAnimation.call(chart); - } else if (d.state === 'restart') { - config.plot_day = - chart.controls.studyDayRange[0] > 0 ? chart.controls.studyDayRange[0] : 0; - chart.controls.studyDayInput.node().value = config.plot_day; - chart.draw(); - - startAnimation.call(chart); - } else { - stopAnimation.call(chart); - } - }); - } - - function initStudyDayControl() { - var chart = this; - var config = this.config; - - // Move the study day control beneath the chart - chart.controls.secondary = chart.wrap - .insert('div', 'div.footnote') - .attr('class', 'wc-controls secondary-controls'); - - var removed = chart.controls.wrap - .selectAll('div') - .filter(function (controlInput) { - return controlInput.label === 'Study Day'; - }) - .remove(); - - chart.controls.studyDayControlWrap = chart.controls.secondary.append(function () { - return removed.node(); - }); - - //convert control to a slider - chart.controls.studyDayInput = chart.controls.studyDayControlWrap.select('input'); - chart.controls.studyDayInput.attr('type', 'range'); - - //set min and max values and add annotations - chart.controls.studyDayRange = d3.extent( - chart.imputed_data.filter(function (f) { - return f.key_measure; - }), - function (d) { - return d[config.studyday_col]; - } - ); - chart.controls.studyDayInput.attr('min', chart.controls.studyDayRange[0]); - chart.controls.studyDayInput.attr('max', chart.controls.studyDayRange[1]); - - chart.controls.studyDayControlWrap - .insert('span', 'input') - .attr('class', 'span-description') - .style('display', 'inline-block') - .style('padding-right', '0.2em') - .text(chart.controls.studyDayRange[0]); - chart.controls.studyDayControlWrap - .append('span') - .attr('class', 'span-description') - .style('display', 'inline-block') - .style('padding-left', '0.2em') - .text(chart.controls.studyDayRange[1]); - - //initialize plot_day to day 0 or the min value, whichever is greater - if (config.plot_day === null) { - config.plot_day = - chart.controls.studyDayRange[0] > 0 ? chart.controls.studyDayRange[0] : 0; - chart.controls.studyDayInput.node().value = config.plot_day; - } - - initPlayButton.call(this); - } - - function onLayout() { - layoutPanels.call(this); - - //init messages section - init$1.call(this); - initCustomWarning.call(this); - initDroppedRowsWarning.call(this); - - initTitle.call(this); - addDownloadButton.call(this); - - addFootnote.call(this); - formatRRatioControl.call(this); - initQuadrants.call(this); - initRugs.call(this); - initVisitPath.call(this); - initParticipantDetails.call(this); - initResetButton.call(this); - customizePlotStyleToggle.call(this); - initDisplayControl.call(this); - initControlLabels.call(this); - initEmptyChartWarning.call(this); - initStudyDayControl.call(this); - relabelMeasureControls.call(this); - } - - function updateAxisSettings() { - var config = this.config; - var unit = - config.display == 'relative_uln' - ? ' [xULN]' - : config.display == 'relative_baseline' - ? ' [xBaseline]' - : config.display == 'absolute' - ? ' [raw values]' - : null; - - //Update axis labels. - config.x.label = config.measure_values[config.x.column] + unit; - config.y.label = config.measure_values[config.y.column] + unit; - } - - function updateControlCutpointLabels() { - var config = this.config; - if ( - this.controls.config.inputs.find(function (input) { - return input.description === 'X-axis Reference Line'; - }) - ) - this.controls.wrap - .selectAll('.control-group') - .filter(function (d) { - return d.description === 'X-axis Reference Line'; - }) - .select('.wc-control-label') - .text(config.measure_values[config.x.column] + ' Reference Line'); - if ( - this.controls.config.inputs.find(function (input) { - return input.description === 'Y-axis Reference Line'; - }) - ) - this.controls.wrap - .selectAll('.control-group') - .filter(function (d) { - return d.description === 'Y-axis Reference Line'; - }) - .select('.wc-control-label') - .text(config.measure_values[config.y.column] + ' Reference Line'); - } - - function setMaxRRatio() { - var chart = this; - var config = this.config; - var r_ratio_wrap = chart.controls.wrap.selectAll('.control-group').filter(function (d) { - return d.option === 'r_ratio[0]'; - }); - - //if no max value is defined, use the max value from the data - if (this.config.r_ratio_filter) { - if (!config.r_ratio[1]) { - var raw_max_r_ratio = d3.max(this.raw_data, function (d) { - return d.rRatio_max; - }); - config.max_r_ratio = Math.ceil(raw_max_r_ratio * 10) / 10; //round up to the nearest 0.1 - config.r_ratio[1] = config.max_r_ratio; - chart.controls.wrap - .selectAll('.control-group') - .filter(function (d) { - return d.option === 'r_ratio[0]'; - }) - .select('input#r_ratio_max') - .property('value', config.max_r_ratio); - } - - //make sure r_ratio[0] <= r_ratio[1] - if (config.r_ratio[0] > config.r_ratio[1]) { - config.r_ratio = config.r_ratio.reverse(); - r_ratio_wrap.select('input#r_ratio_min').property('value', config.r_ratio[0]); - r_ratio_wrap.select('input#r_ratio_max').property('value', config.r_ratio[1]); - } - - //Define flag given r-ratio minimum. - this.raw_data.forEach(function (participant_obj) { - var aboveMin = participant_obj.rRatio >= config.r_ratio[0]; - var belowMax = participant_obj.rRatio <= config.r_ratio[1]; - participant_obj.rRatioFlag = aboveMin & belowMax ? 'Y' : 'N'; - }); - } - } - - 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 calculateRRatios(d, participant_obj) { - var chart = this; - var config = this.config; - - // R-ratio should be the ratio of ALT to ALP - - // For current time point or maximal values (depends on view) - participant_obj.rRatio_current = - participant_obj['ALT_relative_uln'] / participant_obj['ALP_relative_uln']; - - //get r-ratio data for every visit where both ALT and ALP are available - var allMatches = chart.imputed_data.filter(function (f) { - return f[config.id_col] == participant_obj[config.id_col]; - }); - - var raw_alt = allMatches - .filter(function (d) { - return d[config.measure_col] == config.measure_values.ALT; - }) - .map(function (m) { - m.day = m[config.studyday_col]; - m.alt_relative_uln = m.relative_uln; - return m; - }); - - participant_obj.rRatio_raw = allMatches - .filter(function (f) { - return f[config.measure_col] == config.measure_values.ALP; - }) - .map(function (m) { - m.day = m[config.studyday_col]; - m.alp_relative_uln = m.relative_uln; - return m; - }) - .filter(function (f) { - var matched_alt = raw_alt.find(function (fi) { - return fi.day == f.day; - }); - f.alt_relative_uln = matched_alt ? matched_alt.relative_uln : null; - f.rRatio = f['alt_relative_uln'] / f['alp_relative_uln']; - f.value = f.rRatio; - return f.rRatio; - }); - - //max rRatios across visits - participant_obj.rRatio_max = d3.max(participant_obj.rRatio_raw, function (f) { - return f.rRatio; - }); //max rRatio for all visits - participant_obj.rRatio_max_anly = d3.max( - participant_obj.rRatio_raw.filter(function (f) { - return f.analysisFlag; - }), - function (f) { - return f.rRatio; - } - ); - - // rRatio at time of max ALT - var maxAltRecord = participant_obj.rRatio_raw - .filter(function (f) { - return f.analysisFlag; - }) - .sort(function (a, b) { - return b.alt_relative_uln - a.alt_relative_uln; //descending sort (so max is first value) - })[0]; - - participant_obj.rRatio_max_alt = maxAltRecord ? maxAltRecord.rRatio : null; - - // Use the r ratio at the tme of the max ALT value for standard eDish, otherwise use rRatio from the current time point - participant_obj.rRatio = config.plot_max_values - ? participant_obj.rRatio_max_alt - : participant_obj.rRatio_current; - } - - function getMaxValues(d) { - var chart = this; - var config = this.config; - - var participant_obj = {}; - participant_obj.days_x = null; - participant_obj.days_y = null; - participant_obj.outOfRange = false; - Object.keys(config.measure_values).forEach(function (mKey) { - //get all raw data for the current measure - var all_matches = d - .filter(function (f) { - return config.measure_values[mKey] == f[config.measure_col]; - }) //get matching measures - .sort(function (a, b) { - return a[config.studyday_col] - b[config.studyday_col]; - }); - - //Drop Participants with no analysis data - var analysis_matches = all_matches.filter(function (f) { - return f.analysisFlag; - }); - participant_obj.drop_participant = analysis_matches.length === 0; - - if (participant_obj.drop_participant) { - if (config.debug) { - console.warn( - 'No analysis records found for ' + d[0][config.id_col] + ' for ' + mKey - ); - } - - participant_obj.drop_reason = - 'No analysis results found for 1+ key measure, including ' + mKey + '.'; - } else { - //keep an array of all [value, studyday] pairs for the measure - participant_obj[mKey + '_raw'] = all_matches.map(function (m, i) { - return { - value: m[config.display], - day: m[config.studyday_col], - analysisFlag: m.analysisFlag - }; - }); - - var currentRecord = null; - //get the current record for each participant - if (config.plot_max_values) { - //get record with maximum value for the current display type - var max_value = d3.max(analysis_matches, function (d) { - return +d[config.display]; - }); - var currentRecords = analysis_matches.filter(function (d) { - return max_value == +d[config.display]; - }); - if (config.debug & (currentRecords.length > 1)) { - var firstDay = currentRecords[0][config.studyday_col]; - var id = currentRecords[0][config.id_col]; - console.warn( - 'Found duplicate maximum ' + - mKey + - ' values for ' + - id + - '. Using first value from study day ' + - firstDay + - ' for timing calculations.' - ); - } - var currentRecord = currentRecords[0]; - participant_obj[mKey] = +currentRecord[config.display]; - } else { - //see if all selected config.plot_day was while participant was enrolled - var first = all_matches[0]; - var last = all_matches[all_matches.length - 1]; - - if ([config.x.column, config.y.column].includes(mKey)) { - // out-of-range should be calculated study day of with x- and y-axis measures - var before = config.plot_day < first[config.studyday_col]; - var after = config.plot_day > last[config.studyday_col]; - participant_obj.outOfRange = participant_obj.outOfRange || before || after; - } - - //get the most recent measure on or before config.plot_day - var onOrBefore = all_matches.filter(function (di) { - return di[config.studyday_col] <= config.plot_day; - }); - var latest = onOrBefore[onOrBefore.length - 1]; - - currentRecord = latest ? latest : first; - participant_obj[mKey] = currentRecord[config.display]; - } - // var currentRecord = all_matches.find(d => participant_obj[mKey] == +d[config.display]); - - //map all measure specific values - config.flat_cols.forEach(function (col) { - participant_obj[mKey + '_' + col] = currentRecord ? currentRecord[col] : null; - }); - - //determine whether the value is above the specified threshold - if (config.cuts[mKey][config.display]) { - config.show_quadrants = true; - participant_obj[mKey + '_cut'] = config.cuts[mKey][config.display]; - participant_obj[mKey + '_flagged'] = - participant_obj[mKey] >= participant_obj[mKey + '_cut']; - } else { - config.show_quadrants = false; - participant_obj[mKey + '_cut'] = null; - participant_obj[mKey + '_flagged'] = null; - } - - //save study days for each axis; - if (currentRecord) { - if (mKey == config.x.column) - participant_obj.days_x = currentRecord[config.studyday_col]; - if (mKey == config.y.column) - participant_obj.days_y = currentRecord[config.studyday_col]; - } - } - }); - - //Add participant level metadata - addParticipantLevelMetadata.call(chart, d, participant_obj); - - //Calculate ratios between measures. - calculateRRatios.call(chart, d, participant_obj); - - //calculate the day difference between x and y and total day range for all measure values - participant_obj.day_diff = Math.abs(participant_obj.days_x - participant_obj.days_y); - participant_obj.day_range = d3.extent(d, function (d) { - return d[config.studyday_col]; - }); - - return participant_obj; - } - - function getFlatCols() { - var config = this.config; - - //get list of columns to flatten - var flat_cols = []; - var user_cols = [ - 'measure_col', - 'value_col', - 'studyday_col', - 'normal_col_low', - 'normal_col_high' - ]; - var derived_cols = [ - 'absolute', - 'relative_uln', - 'relative_baseline', - 'baseline_absolute', - 'analysisFlag' - ]; - - // populate the full list of columns to flatten labels - user_cols.forEach(function (d) { - if (Array.isArray(d)) { - d.forEach(function (di) { - flat_cols.push( - di.hasOwnProperty('value_col') ? config[di.value_col] : config[di] - ); - }); - } else { - flat_cols.push(d.hasOwnProperty('value_col') ? config[d.value_col] : config[d]); - } - }); - - //merge in the derived columns - return d3.merge([flat_cols, derived_cols]); - } - - function calculatePalt(pt) { - // Calculates the pAlt value for the given participant ID - // For more on PAlt see the following paper: A Rapid Method to Estimate Hepatocyte Loss Due to Drug-Induced Liver Injury by Chung et al - // Requires: Baseline visit - // Assumes: Units on Alt are IU/L - var config = this.config; - - //Get a list of raw post-baseline ALT values - var alt_values = pt.values.raw - .filter(function (f) { - return f[config.measure_col] == config.measure_values.ALT; - }) - .filter(function (f) { - return f.paltFlag; - }) - .map(function (d) { - var obj = {}; - obj.value = d[config.value_col]; - obj.day = d[config.studyday_col]; - obj.hour = d.day * 24; - return obj; - }); - if (alt_values.length > 1) { - //get peak alt value - var alt_peak = d3.max(alt_values, function (f) { - return f.value; - }); - - //caluculate ALT AUC - var alt_auc = d3.sum(alt_values, function (d, i) { - if (i < alt_values.length - 1) { - var di = d; - var vi = di.value; - var hi = di.hour; - var dii = alt_values[i + 1]; - var vii = dii.value; - var hii = dii.hour; - var v_avg = (vii + vi) / 2; - var h_diff = hii - hi; - var segment_auc = v_avg * h_diff; - - return segment_auc; - } else { - return 0; - } - }); - - //calculate pAlt (ALT AUC + AltPeak ^ 0.18) - var p_alt = (alt_auc * Math.pow(alt_peak, 0.18)) / 100000; - var p_alt_rounded = d3.format('0.2f')(p_alt); - var alt_auc_rounded = d3.format('0.2f')(alt_auc); - var alt_peak_rounded = d3.format('0.2f')(alt_peak); - var note = - 'NOTE: ' + - 'For this participant, PALT was calculated as: ' + - 'ALT AUC * Peak ALT 0.18 / 105 = ' + - alt_auc_rounded + - ' * ' + - alt_peak_rounded + - ' 0.18 / 105 = ' + - p_alt_rounded + - '
' + - '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>} data - Pairs of observed x-y values - * @param {Array>} results - Pairs of observed predicted x-y values - * - * @return {number} - The r^2 value, or NaN if one cannot be calculated. - */ - function determinationCoefficient(data, results) { - var predictions = []; - var observations = []; - - data.forEach(function (d, i) { - if (d[1] !== null) { - observations.push(d); - predictions.push(results[i]); - } - }); - - var sum = observations.reduce(function (a, observation) { - return a + observation[1]; - }, 0); - var mean = sum / observations.length; - - var ssyy = observations.reduce(function (a, observation) { - var difference = observation[1] - mean; - return a + difference * difference; - }, 0); - - var sse = observations.reduce(function (accum, observation, index) { - var prediction = predictions[index]; - var residual = observation[1] - prediction[1]; - return accum + residual * residual; - }, 0); - - return 1 - sse / ssyy; - } - - /** - * Determine the solution of a system of linear equations A * x = b using - * Gaussian elimination. - * - * @param {Array>} input - A 2-d matrix of data in row-major form [ A | b ] - * @param {number} order - How many degrees to solve for - * - * @return {Array} - Vector of normalized solution coefficients matrix (x) - */ - function gaussianElimination(input, order) { - var matrix = input; - var n = input.length - 1; - var coefficients = [order]; - - for (var i = 0; i < n; i++) { - var maxrow = i; - for (var j = i + 1; j < n; j++) { - if (Math.abs(matrix[i][j]) > Math.abs(matrix[i][maxrow])) { - maxrow = j; - } - } - - for (var k = i; k < n + 1; k++) { - var tmp = matrix[k][i]; - matrix[k][i] = matrix[k][maxrow]; - matrix[k][maxrow] = tmp; - } - - for (var _j = i + 1; _j < n; _j++) { - for (var _k = n; _k >= i; _k--) { - matrix[_k][_j] -= (matrix[_k][i] * matrix[i][_j]) / matrix[i][i]; - } - } - } - - for (var _j2 = n - 1; _j2 >= 0; _j2--) { - var total = 0; - for (var _k2 = _j2 + 1; _k2 < n; _k2++) { - total += matrix[_k2][_j2] * coefficients[_k2]; - } - - coefficients[_j2] = (matrix[n][_j2] - total) / matrix[_j2][_j2]; - } - - return coefficients; - } - - /** - * Round a number to a precision, specificed in number of decimal places - * - * @param {number} number - The number to round - * @param {number} precision - The number of decimal places to round to: - * > 0 means decimals, < 0 means powers of 10 - * - * - * @return {numbr} - The number, rounded - */ - function round(number, precision) { - var factor = Math.pow(10, precision); - return Math.round(number * factor) / factor; - } - - /** - * The set of all fitting methods - * - * @namespace - */ - var methods = { - linear: function linear(data, options) { - var sum = [0, 0, 0, 0, 0]; - var len = 0; - - for (var n = 0; n < data.length; n++) { - if (data[n][1] !== null) { - len++; - sum[0] += data[n][0]; - sum[1] += data[n][1]; - sum[2] += data[n][0] * data[n][0]; - sum[3] += data[n][0] * data[n][1]; - sum[4] += data[n][1] * data[n][1]; - } - } - - var run = len * sum[2] - sum[0] * sum[0]; - var rise = len * sum[3] - sum[0] * sum[1]; - var gradient = run === 0 ? 0 : round(rise / run, options.precision); - var intercept = round( - sum[1] / len - (gradient * sum[0]) / len, - options.precision - ); - - var predict = function predict(x) { - return [ - round(x, options.precision), - round(gradient * x + intercept, options.precision) - ]; - }; - - var points = data.map(function (point) { - return predict(point[0]); - }); - - return { - points: points, - predict: predict, - equation: [gradient, intercept], - r2: round(determinationCoefficient(data, points), options.precision), - string: - intercept === 0 - ? 'y = ' + gradient + 'x' - : 'y = ' + gradient + 'x + ' + intercept - }; - }, - exponential: function exponential(data, options) { - var sum = [0, 0, 0, 0, 0, 0]; - - for (var n = 0; n < data.length; n++) { - if (data[n][1] !== null) { - sum[0] += data[n][0]; - sum[1] += data[n][1]; - sum[2] += data[n][0] * data[n][0] * data[n][1]; - sum[3] += data[n][1] * Math.log(data[n][1]); - sum[4] += data[n][0] * data[n][1] * Math.log(data[n][1]); - sum[5] += data[n][0] * data[n][1]; - } - } - - var denominator = sum[1] * sum[2] - sum[5] * sum[5]; - var a = Math.exp((sum[2] * sum[3] - sum[5] * sum[4]) / denominator); - var b = (sum[1] * sum[4] - sum[5] * sum[3]) / denominator; - var coeffA = round(a, options.precision); - var coeffB = round(b, options.precision); - var predict = function predict(x) { - return [ - round(x, options.precision), - round(coeffA * Math.exp(coeffB * x), options.precision) - ]; - }; - - var points = data.map(function (point) { - return predict(point[0]); - }); - - return { - points: points, - predict: predict, - equation: [coeffA, coeffB], - string: 'y = ' + coeffA + 'e^(' + coeffB + 'x)', - r2: round(determinationCoefficient(data, points), options.precision) - }; - }, - logarithmic: function logarithmic(data, options) { - var sum = [0, 0, 0, 0]; - var len = data.length; - - for (var n = 0; n < len; n++) { - if (data[n][1] !== null) { - sum[0] += Math.log(data[n][0]); - sum[1] += data[n][1] * Math.log(data[n][0]); - sum[2] += data[n][1]; - sum[3] += Math.pow(Math.log(data[n][0]), 2); - } - } - - var a = (len * sum[1] - sum[2] * sum[0]) / (len * sum[3] - sum[0] * sum[0]); - var coeffB = round(a, options.precision); - var coeffA = round((sum[2] - coeffB * sum[0]) / len, options.precision); - - var predict = function predict(x) { - return [ - round(x, options.precision), - round( - round(coeffA + coeffB * Math.log(x), options.precision), - options.precision - ) - ]; - }; - - var points = data.map(function (point) { - return predict(point[0]); - }); - - return { - points: points, - predict: predict, - equation: [coeffA, coeffB], - string: 'y = ' + coeffA + ' + ' + coeffB + ' ln(x)', - r2: round(determinationCoefficient(data, points), options.precision) - }; - }, - power: function power(data, options) { - var sum = [0, 0, 0, 0, 0]; - var len = data.length; - - for (var n = 0; n < len; n++) { - if (data[n][1] !== null) { - sum[0] += Math.log(data[n][0]); - sum[1] += Math.log(data[n][1]) * Math.log(data[n][0]); - sum[2] += Math.log(data[n][1]); - sum[3] += Math.pow(Math.log(data[n][0]), 2); - } - } - - var b = (len * sum[1] - sum[0] * sum[2]) / (len * sum[3] - Math.pow(sum[0], 2)); - var a = (sum[2] - b * sum[0]) / len; - var coeffA = round(Math.exp(a), options.precision); - var coeffB = round(b, options.precision); - - var predict = function predict(x) { - return [ - round(x, options.precision), - round( - round(coeffA * Math.pow(x, coeffB), options.precision), - options.precision - ) - ]; - }; - - var points = data.map(function (point) { - return predict(point[0]); - }); - - return { - points: points, - predict: predict, - equation: [coeffA, coeffB], - string: 'y = ' + coeffA + 'x^' + coeffB, - r2: round(determinationCoefficient(data, points), options.precision) - }; - }, - polynomial: function polynomial(data, options) { - var lhs = []; - var rhs = []; - var a = 0; - var b = 0; - var len = data.length; - var k = options.order + 1; - - for (var i = 0; i < k; i++) { - for (var l = 0; l < len; l++) { - if (data[l][1] !== null) { - a += Math.pow(data[l][0], i) * data[l][1]; - } - } - - lhs.push(a); - a = 0; - - var c = []; - for (var j = 0; j < k; j++) { - for (var _l = 0; _l < len; _l++) { - if (data[_l][1] !== null) { - b += Math.pow(data[_l][0], i + j); - } - } - c.push(b); - b = 0; - } - rhs.push(c); - } - rhs.push(lhs); - - var coefficients = gaussianElimination(rhs, k).map(function (v) { - return round(v, options.precision); - }); - - var predict = function predict(x) { - return [ - round(x, options.precision), - round( - coefficients.reduce(function (sum, coeff, power) { - return sum + coeff * Math.pow(x, power); - }, 0), - options.precision - ) - ]; - }; - - var points = data.map(function (point) { - return predict(point[0]); - }); - - var string = 'y = '; - for (var _i = coefficients.length - 1; _i >= 0; _i--) { - if (_i > 1) { - string += coefficients[_i] + 'x^' + _i + ' + '; - } else if (_i === 1) { - string += coefficients[_i] + 'x + '; - } else { - string += coefficients[_i]; - } - } - - return { - string: string, - points: points, - predict: predict, - equation: [].concat(_toConsumableArray(coefficients)).reverse(), - r2: round(determinationCoefficient(data, points), options.precision) - }; - } - }; - - function createWrapper() { - var reduce = function reduce(accumulator, name) { - return _extends( - { - _round: round - }, - accumulator, - _defineProperty({}, name, function (data, supplied) { - return methods[name](data, _extends({}, DEFAULT_OPTIONS, supplied)); - }) - ); - }; - - return Object.keys(methods).reduce(reduce, {}); - } - - module.exports = createWrapper(); - }); - }); - - function addRegressionLine() { - if (this.config.add_regression_line) { - var chart = this; - var config = this.config; - - // map chart data to array and calculate regression using regression-js - var arrayData = chart.filtered_data - .filter(function (f) { - return !isNaN(f.delta_x); - }) - .filter(function (f) { - return !isNaN(f.delta_y); - }) - .map(function (d) { - return [+d.delta_x, +d.delta_y]; - }); - - var result = regression.linear(arrayData); - - //calculate predicted values for min and max points on the chart - var min_x = chart.x_dom[0]; - var min_xy = result.predict(min_x); - var max_x = chart.x_dom[1]; - var max_xy = result.predict(max_x); - - //draw the regression line - var line = d3.svg - .line() - .x(function (d) { - return chart.x(d[0]); - }) - .y(function (d) { - return chart.y(d[1]); - }); - chart.svg.selectAll('.regressionLine').remove(); - chart.svg - .append('path') - .classed('regressionLine', true) - .datum([min_xy, max_xy]) - .attr('d', line) - .attr('stroke', 'black') - .attr('stroke-dasharray', '3,5'); - - //add footnote with R2 and exact calculation - chart.wrap.select('span.regression-note').remove(); - chart.wrap - .append('span') - .classed('regression-note', true) - .html( - 'The dashed line shows the result of a simple linear regression. Additional details are shown below.
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; -}); From 8a4ca2c13cd348b85b2f7613aff35bf09a73aa15 Mon Sep 17 00:00:00 2001 From: jwildfire Date: Tue, 13 Apr 2021 09:20:47 -0400 Subject: [PATCH 6/7] sets name before reordering --- R/makeChartConfig.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/makeChartConfig.R b/R/makeChartConfig.R index aae70c18..5426ce4e 100644 --- a/R/makeChartConfig.R +++ b/R/makeChartConfig.R @@ -65,9 +65,8 @@ makeChartConfig <- function(dirs, sourceFiles=TRUE){ return(chart) }) - charts <- charts[order(purrr::map_dbl(charts, function(chart) chart$order))] - 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=", ")) From 140bd88e1119f6d589db800e3d5ada1089ea74db Mon Sep 17 00:00:00 2001 From: jwildfire Date: Tue, 13 Apr 2021 12:46:26 -0400 Subject: [PATCH 7/7] add warning when no charts are found. remove stray renderers. update mappingSelect test. --- R/makeChartConfig.R | 16 +- inst/htmlwidgets/chartRenderer.js | 38 - inst/htmlwidgets/chartRenderer.yaml | 47 - inst/htmlwidgets/hepexplorer.js | 26 - inst/htmlwidgets/hepexplorer.yaml | 14 - inst/htmlwidgets/lib/d3-3.5.17/LICENSE | 26 - inst/htmlwidgets/lib/d3-3.5.17/d3.v3.min.js | 5 - .../lib/webcharts-1.11.6/webcharts.css | 635 --- .../lib/webcharts-1.11.6/webcharts.js | 4542 ----------------- tests/testthat/test_mod_mappingSelect.R | 2 +- 10 files changed, 14 insertions(+), 5337 deletions(-) delete mode 100644 inst/htmlwidgets/chartRenderer.js delete mode 100644 inst/htmlwidgets/chartRenderer.yaml delete mode 100644 inst/htmlwidgets/hepexplorer.js delete mode 100644 inst/htmlwidgets/hepexplorer.yaml delete mode 100644 inst/htmlwidgets/lib/d3-3.5.17/LICENSE delete mode 100644 inst/htmlwidgets/lib/d3-3.5.17/d3.v3.min.js delete mode 100644 inst/htmlwidgets/lib/webcharts-1.11.6/webcharts.css delete mode 100644 inst/htmlwidgets/lib/webcharts-1.11.6/webcharts.js diff --git a/R/makeChartConfig.R b/R/makeChartConfig.R index 5426ce4e..644c369e 100644 --- a/R/makeChartConfig.R +++ b/R/makeChartConfig.R @@ -23,9 +23,20 @@ makeChartConfig <- function(dirs, sourceFiles=TRUE){ # Use the charts settings saved in safetycharts if no path is provided. if(missing(dirs) || is.null(dirs)){ + safetyChartsFound<-FALSE for(lib in .libPaths()){ - dirs<-paste(lib,'safetyCharts','config', sep="/") - if(file.exists(dirs)) break + 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.") } } @@ -103,6 +114,5 @@ makeChartConfig <- function(dirs, sourceFiles=TRUE){ return(chart) } ) - return(charts) } 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/d3-3.5.17/LICENSE b/inst/htmlwidgets/lib/d3-3.5.17/LICENSE deleted file mode 100644 index a31bde44..00000000 --- a/inst/htmlwidgets/lib/d3-3.5.17/LICENSE +++ /dev/null @@ -1,26 +0,0 @@ -Copyright (c) 2010-2016, Michael Bostock -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* The name Michael Bostock may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY -OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/inst/htmlwidgets/lib/d3-3.5.17/d3.v3.min.js b/inst/htmlwidgets/lib/d3-3.5.17/d3.v3.min.js deleted file mode 100644 index 16648730..00000000 --- a/inst/htmlwidgets/lib/d3-3.5.17/d3.v3.min.js +++ /dev/null @@ -1,5 +0,0 @@ -!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function i(n){return!isNaN(n)}function u(n){return{left:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)<0?r=u+1:i=u}return r},right:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)>0?i=u:r=u+1}return r}}}function o(n){return n.length}function a(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function f(n){return(n+="")===bo||n[0]===_o?_o+n:n}function s(n){return(n+="")[0]===_o?n.slice(1):n}function h(n){return f(n)in this._}function p(n){return(n=f(n))in this._&&delete this._[n]}function g(){var n=[];for(var t in this._)n.push(s(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function y(){this._=Object.create(null)}function m(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=wo.length;r>e;++e){var i=wo[e]+t;if(i in n)return i}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,i=-1,u=r.length;++ie;e++)for(var i,u=n[e],o=0,a=u.length;a>o;o++)(i=u[o])&&t(i,o,e);return n}function Z(n){return ko(n,qo),n}function V(n){var t,e;return function(r,i,u){var o,a=n[u].update,l=a.length;for(u!=e&&(e=u,t=0),i>=t&&(t=i+1);!(o=a[t])&&++t0&&(n=n.slice(0,a));var c=To.get(n);return c&&(n=c,l=B),a?t?i:r:t?b:u}function $(n,t){return function(e){var r=ao.event;ao.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ao.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Do,i="click"+r,u=ao.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ro&&(Ro="onselectstart"in e?!1:x(e.style,"userSelect")),Ro){var o=n(e).style,a=o[Ro];o[Ro]="none"}return function(n){if(u.on(r,null),Ro&&(o[Ro]=a),n){var t=function(){u.on(i,null)};u.on(i,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var i=r.createSVGPoint();if(0>Po){var u=t(n);if(u.scrollX||u.scrollY){r=ao.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var o=r[0][0].getScreenCTM();Po=!(o.f||o.e),r.remove()}}return Po?(i.x=e.pageX,i.y=e.pageY):(i.x=e.clientX,i.y=e.clientY),i=i.matrixTransform(n.getScreenCTM().inverse()),[i.x,i.y]}var a=n.getBoundingClientRect();return[e.clientX-a.left-n.clientLeft,e.clientY-a.top-n.clientTop]}function G(){return ao.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?Fo:Math.acos(n)}function tn(n){return n>1?Io:-1>n?-Io:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function on(n){return(n=Math.sin(n/2))*n}function an(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?u+(o-u)*n/60:180>n?o:240>n?u+(o-u)*(240-n)/60:u}function i(n){return Math.round(255*r(n))}var u,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,u=2*e-o,new mn(i(n+120),i(n),i(n-120))}function fn(n,t,e){return this instanceof fn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof fn?new fn(n.h,n.c,n.l):n instanceof hn?gn(n.l,n.a,n.b):gn((n=Sn((n=ao.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new fn(n,t,e)}function sn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Yo)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof fn?sn(n.h,n.c,n.l):Sn((n=mn(n)).r,n.g,n.b):new hn(n,t,e)}function pn(n,t,e){var r=(n+16)/116,i=r+t/500,u=r-e/200;return i=vn(i)*na,r=vn(r)*ta,u=vn(u)*ea,new mn(yn(3.2404542*i-1.5371385*r-.4985314*u),yn(-.969266*i+1.8760108*r+.041556*u),yn(.0556434*i-.2040259*r+1.0572252*u))}function gn(n,t,e){return n>0?new fn(Math.atan2(e,t)*Zo,Math.sqrt(t*t+e*e),n):new fn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function yn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function mn(n,t,e){return this instanceof mn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof mn?new mn(n.r,n.g,n.b):_n(""+n,mn,cn):new mn(n,t,e)}function Mn(n){return new mn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,i,u,o=0,a=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(i=r[2].split(","),r[1]){case"hsl":return e(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return t(Nn(i[0]),Nn(i[1]),Nn(i[2]))}return(u=ua.get(n))?t(u.r,u.g,u.b):(null==n||"#"!==n.charAt(0)||isNaN(u=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&u)>>4,o=o>>4|o,a=240&u,a=a>>4|a,l=15&u,l=l<<4|l):7===n.length&&(o=(16711680&u)>>16,a=(65280&u)>>8,l=255&u)),t(o,a,l))}function wn(n,t,e){var r,i,u=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-u,l=(o+u)/2;return a?(i=.5>l?a/(o+u):a/(2-o-u),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=NaN,i=l>0&&1>l?0:r),new ln(r,i,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/na),i=dn((.2126729*n+.7151522*t+.072175*e)/ta),u=dn((.0193339*n+.119192*t+.9503041*e)/ea);return hn(116*i-16,500*(r-i),200*(i-u))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function i(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(u,l)}catch(r){return void o.error.call(u,r)}o.load.call(u,n)}else o.error.call(u,l)}var u={},o=ao.dispatch("beforesend","progress","load","error"),a={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=i:l.onreadystatechange=function(){l.readyState>3&&i()},l.onprogress=function(n){var t=ao.event;ao.event=n;try{o.progress.call(u,l)}finally{ao.event=t}},u.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",u)},u.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",u):t},u.responseType=function(n){return arguments.length?(c=n,u):c},u.response=function(n){return e=n,u},["get","post"].forEach(function(n){u[n]=function(){return u.send.apply(u,[n].concat(co(arguments)))}}),u.send=function(e,r,i){if(2===arguments.length&&"function"==typeof r&&(i=r,r=null),l.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),l.setRequestHeader)for(var f in a)l.setRequestHeader(f,a[f]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=i&&u.on("error",i).on("load",function(n){i(null,n)}),o.beforesend.call(u,l),l.send(null==r?null:r),u},u.abort=function(){return l.abort(),u},ao.rebind(u,o,"on"),null==r?u:u.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var i=e+t,u={c:n,t:i,n:null};return aa?aa.n=u:oa=u,aa=u,la||(ca=clearTimeout(ca),la=1,fa(Tn)),u}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(ca),ca=setTimeout(Tn,t)),la=0):(la=1,fa(Tn))}function Rn(){for(var n=Date.now(),t=oa;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=oa,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function jn(n){var t=n.decimal,e=n.thousands,r=n.grouping,i=n.currency,u=r&&e?function(n,t){for(var i=n.length,u=[],o=0,a=r[0],l=0;i>0&&a>0&&(l+a+1>t&&(a=Math.max(1,t-l)),u.push(n.substring(i-=a,i+a)),!((l+=a+1)>t));)a=r[o=(o+1)%r.length];return u.reverse().join(e)}:m;return function(n){var e=ha.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",l=e[4]||"",c=e[5],f=+e[6],s=e[7],h=e[8],p=e[9],g=1,v="",d="",y=!1,m=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===o)&&(c=r="0",o="="),p){case"n":s=!0,p="g";break;case"%":g=100,d="%",p="f";break;case"p":g=100,d="%",p="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+p.toLowerCase());case"c":m=!1;case"d":y=!0,h=0;break;case"s":g=-1,p="r"}"$"===l&&(v=i[0],d=i[1]),"r"!=p||h||(p="g"),null!=h&&("g"==p?h=Math.max(1,Math.min(21,h)):"e"!=p&&"f"!=p||(h=Math.max(0,Math.min(20,h)))),p=pa.get(p)||Fn;var M=c&&s;return function(n){var e=d;if(y&&n%1)return"";var i=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>g){var l=ao.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=g;n=p(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=m?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&s&&(x=u(x,1/0));var S=v.length+x.length+b.length+(M?0:i.length),k=f>S?new Array(S=f-S+1).join(r):"";return M&&(x=u(k+x,k.length?f-b.length:1/0)),i+=v,n=x+b,("<"===o?i+n+k:">"===o?k+i+n:"^"===o?k.substring(0,S>>=1)+i+n+k.substring(S):i+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=u(e,1);return r-t>t-e?e:r}function i(e){return t(e=n(new va(e-1)),1),e}function u(n,e){return t(n=new va(+n),e),n}function o(n,r,u){var o=i(n),a=[];if(u>1)for(;r>o;)e(o)%u||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{va=Hn;var r=new Hn;return r._=n,o(r,t,e)}finally{va=Date}}n.floor=n,n.round=r,n.ceil=i,n.offset=u,n.range=o;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(i),l.offset=In(u),l.range=a,n}function In(n){return function(t,e){try{va=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{va=Date}}}function Yn(n){function t(n){function t(t){for(var e,i,u,o=[],a=-1,l=0;++aa;){if(r>=c)return-1;if(i=t.charCodeAt(a++),37===i){if(o=t.charAt(a++),u=C[o in ya?t.charAt(a++):o],!u||(r=u(n,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function f(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var s=n.dateTime,h=n.date,p=n.time,g=n.periods,v=n.days,d=n.shortDays,y=n.months,m=n.shortMonths;t.utc=function(n){function e(n){try{va=Hn;var t=new va;return t._=n,r(t)}finally{va=Date}}var r=t(n);return e.parse=function(n){try{va=Hn;var t=r.parse(n);return t&&t._}finally{va=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=ao.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(y),k=Xn(y),N=Vn(m),E=Xn(m);g.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return m[n.getMonth()]},B:function(n){return y[n.getMonth()]},c:t(s),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+ga.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return g[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(ga.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(ga.mondayOfYear(n),t,2)},x:t(h),X:t(p),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:at,"%":function(){return"%"}},C={a:r,A:i,b:u,B:o,c:a,d:tt,e:tt,H:rt,I:rt,j:et,L:ot,m:nt,M:it,p:f,S:ut,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"",i=(r?-n:n)+"",u=i.length;return r+(e>u?new Array(e-u+1).join(t)+i:i)}function Vn(n){return new RegExp("^(?:"+n.map(ao.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function nt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function it(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ut(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ot(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function at(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=xo(t)/60|0,i=xo(t)%60;return e+Zn(r,"0",2)+Zn(i,"0",2)}function lt(n,t,e){Ma.lastIndex=0;var r=Ma.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*e,l=Math.cos(t),c=Math.sin(t),f=u*c,s=i*l+f*Math.cos(a),h=f*o*Math.sin(a);ka.add(Math.atan2(h,s)),r=n,i=l,u=c}var t,e,r,i,u;Na.point=function(o,a){Na.point=n,r=(t=o)*Yo,i=Math.cos(a=(e=a)*Yo/2+Fo/4),u=Math.sin(a)},Na.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function yt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function mt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return xo(n[0]-t[0])a;++a)i.point((e=n[a])[0],e[1]);return void i.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,u.push(l),o.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,u.push(l),o.push(c)}}),o.sort(t),qt(u),qt(o),u.length){for(var a=0,l=e,c=o.length;c>a;++a)o[a].e=l=!l;for(var f,s,h=u[0];;){for(var p=h,g=!0;p.v;)if((p=p.n)===h)return;f=p.z,i.lineStart();do{if(p.v=p.o.v=!0,p.e){if(g)for(var a=0,c=f.length;c>a;++a)i.point((s=f[a])[0],s[1]);else r(p.x,p.n.x,1,i);p=p.n}else{if(g){f=p.p.z;for(var a=f.length-1;a>=0;--a)i.point((s=f[a])[0],s[1])}else r(p.x,p.p.x,-1,i);p=p.p}p=p.o,f=p.z,g=!g}while(!p.v);i.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,i=n[0];++r0){for(b||(u.polygonStart(),b=!0),u.lineStart();++o1&&2&t&&e.push(e.pop().concat(e.shift())),p.push(e.filter(Dt))}var p,g,v,d=t(u),y=i.invert(r[0],r[1]),m={point:o,lineStart:l,lineEnd:c,polygonStart:function(){m.point=f,m.lineStart=s,m.lineEnd=h,p=[],g=[]},polygonEnd:function(){m.point=o,m.lineStart=l,m.lineEnd=c,p=ao.merge(p);var n=Ot(y,g);p.length?(b||(u.polygonStart(),b=!0),Lt(p,Ut,n,e,u)):n&&(b||(u.polygonStart(),b=!0),u.lineStart(),e(null,null,1,u),u.lineEnd()),b&&(u.polygonEnd(),b=!1),p=g=null},sphere:function(){u.polygonStart(),u.lineStart(),e(null,null,1,u),u.lineEnd(),u.polygonEnd()}},M=Pt(),x=t(M),b=!1;return m}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Ut(n,t){return((n=n.x)[0]<0?n[1]-Io-Uo:Io-n[1])-((t=t.x)[0]<0?t[1]-Io-Uo:Io-t[1])}function jt(n){var t,e=NaN,r=NaN,i=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(u,o){var a=u>0?Fo:-Fo,l=xo(u-e);xo(l-Fo)0?Io:-Io),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(u,r),t=0):i!==a&&l>=Fo&&(xo(e-i)Uo?Math.atan((Math.sin(t)*(u=Math.cos(r))*Math.sin(e)-Math.sin(r)*(i=Math.cos(t))*Math.sin(n))/(i*u*o)):(t+r)/2}function Ht(n,t,e,r){var i;if(null==n)i=e*Io,r.point(-Fo,i),r.point(0,i),r.point(Fo,i),r.point(Fo,0),r.point(Fo,-i),r.point(0,-i),r.point(-Fo,-i),r.point(-Fo,0),r.point(-Fo,i);else if(xo(n[0]-t[0])>Uo){var u=n[0]a;++a){var c=t[a],f=c.length;if(f)for(var s=c[0],h=s[0],p=s[1]/2+Fo/4,g=Math.sin(p),v=Math.cos(p),d=1;;){d===f&&(d=0),n=c[d];var y=n[0],m=n[1]/2+Fo/4,M=Math.sin(m),x=Math.cos(m),b=y-h,_=b>=0?1:-1,w=_*b,S=w>Fo,k=g*M;if(ka.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),u+=S?b+_*Ho:b,S^h>=e^y>=e){var N=mt(dt(s),dt(n));bt(N);var E=mt(i,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=y,g=M,v=x,s=n}}return(-Uo>u||Uo>u&&-Uo>ka)^1&o}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>u}function e(n){var e,u,l,c,f;return{lineStart:function(){c=l=!1,f=1},point:function(s,h){var p,g=[s,h],v=t(s,h),d=o?v?0:i(s,h):v?i(s+(0>s?Fo:-Fo),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(p=r(e,g),(wt(e,p)||wt(g,p))&&(g[0]+=Uo,g[1]+=Uo,v=t(g[0],g[1]))),v!==l)f=0,v?(n.lineStart(),p=r(g,e),n.point(p[0],p[1])):(p=r(e,g),n.point(p[0],p[1]),n.lineEnd()),e=p;else if(a&&e&&o^v){var y;d&u||!(y=r(g,e,!0))||(f=0,o?(n.lineStart(),n.point(y[0][0],y[0][1]),n.point(y[1][0],y[1][1]),n.lineEnd()):(n.point(y[1][0],y[1][1]),n.lineEnd(),n.lineStart(),n.point(y[0][0],y[0][1])))}!v||e&&wt(e,g)||n.point(g[0],g[1]),e=g,l=v,u=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return f|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),i=dt(t),o=[1,0,0],a=mt(r,i),l=yt(a,a),c=a[0],f=l-c*c;if(!f)return!e&&n;var s=u*l/f,h=-u*c/f,p=mt(o,a),g=xt(o,s),v=xt(a,h);Mt(g,v);var d=p,y=yt(g,d),m=yt(d,d),M=y*y-m*(yt(g,g)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-y-x)/m);if(Mt(b,g),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=xo(E-Fo)E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(xo(b[0]-w)Fo^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-y+x)/m);return Mt(z,g),[b,_t(z)]}}}function i(t,e){var r=o?n:Fo-n,i=0;return-r>t?i|=1:t>r&&(i|=2),-r>e?i|=4:e>r&&(i|=8),i}var u=Math.cos(n),o=u>0,a=xo(u)>Uo,l=ve(n,6*Yo);return Rt(t,e,l,o?[0,-n]:[-Fo,n-Fo])}function Yt(n,t,e,r){return function(i){var u,o=i.a,a=i.b,l=o.x,c=o.y,f=a.x,s=a.y,h=0,p=1,g=f-l,v=s-c;if(u=n-l,g||!(u>0)){if(u/=g,0>g){if(h>u)return;p>u&&(p=u)}else if(g>0){if(u>p)return;u>h&&(h=u)}if(u=e-l,g||!(0>u)){if(u/=g,0>g){if(u>p)return;u>h&&(h=u)}else if(g>0){if(h>u)return;p>u&&(p=u)}if(u=t-c,v||!(u>0)){if(u/=v,0>v){if(h>u)return;p>u&&(p=u)}else if(v>0){if(u>p)return;u>h&&(h=u)}if(u=r-c,v||!(0>u)){if(u/=v,0>v){if(u>p)return;u>h&&(h=u)}else if(v>0){if(h>u)return;p>u&&(p=u)}return h>0&&(i.a={x:l+h*g,y:c+h*v}),1>p&&(i.b={x:l+p*g,y:c+p*v}),i}}}}}}function Zt(n,t,e,r){function i(r,i){return xo(r[0]-n)0?0:3:xo(r[0]-e)0?2:1:xo(r[1]-t)0?1:0:i>0?3:2}function u(n,t){return o(n.x,t.x)}function o(n,t){var e=i(n,1),r=i(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function l(n){for(var t=0,e=d.length,r=n[1],i=0;e>i;++i)for(var u,o=1,a=d[i],l=a.length,c=a[0];l>o;++o)u=a[o],c[1]<=r?u[1]>r&&Q(c,u,n)>0&&++t:u[1]<=r&&Q(c,u,n)<0&&--t,c=u;return 0!==t}function c(u,a,l,c){var f=0,s=0;if(null==u||(f=i(u,l))!==(s=i(a,l))||o(u,a)<0^l>0){do c.point(0===f||3===f?n:e,f>1?r:t);while((f=(f+l+4)%4)!==s)}else c.point(a[0],a[1])}function f(i,u){return i>=n&&e>=i&&u>=t&&r>=u}function s(n,t){f(n,t)&&a.point(n,t)}function h(){C.point=g,d&&d.push(y=[]),S=!0,w=!1,b=_=NaN}function p(){v&&(g(m,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=s,w&&a.lineEnd()}function g(n,t){n=Math.max(-Ha,Math.min(Ha,n)),t=Math.max(-Ha,Math.min(Ha,t));var e=f(n,t);if(d&&y.push([n,t]),S)m=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,y,m,M,x,b,_,w,S,k,N=a,E=Pt(),A=Yt(n,t,e,r),C={point:s,lineStart:h,lineEnd:p,polygonStart:function(){a=E,v=[],d=[],k=!0},polygonEnd:function(){a=N,v=ao.merge(v);var t=l([n,r]),e=k&&t,i=v.length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),c(null,null,1,a),a.lineEnd()),i&&Lt(v,u,t,c,a),a.polygonEnd()),v=d=y=null}};return C}}function Vt(n){var t=0,e=Fo/3,r=ae(n),i=r(t,e);return i.parallels=function(n){return arguments.length?r(t=n[0]*Fo/180,e=n[1]*Fo/180):[t/Fo*180,e/Fo*180]},i}function Xt(n,t){function e(n,t){var e=Math.sqrt(u-2*i*Math.sin(t))/i;return[e*Math.sin(n*=i),o-e*Math.cos(n)]}var r=Math.sin(n),i=(r+Math.sin(t))/2,u=1+r*(2*i-r),o=Math.sqrt(u)/i;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/i,tn((u-(n*n+e*e)*i*i)/(2*i))]},e}function $t(){function n(n,t){Ia+=i*n-r*t,r=n,i=t}var t,e,r,i;$a.point=function(u,o){$a.point=n,t=r=u,e=i=o},$a.lineEnd=function(){n(t,e)}}function Bt(n,t){Ya>n&&(Ya=n),n>Va&&(Va=n),Za>t&&(Za=t),t>Xa&&(Xa=t)}function Wt(){function n(n,t){o.push("M",n,",",t,u)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function i(){o.push("Z")}var u=Jt(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return u=Jt(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Ca+=n,za+=t,++La}function Kt(){function n(n,r){var i=n-t,u=r-e,o=Math.sqrt(i*i+u*u);qa+=o*(t+n)/2,Ta+=o*(e+r)/2,Ra+=o,Gt(t=n,e=r)}var t,e;Wa.point=function(r,i){Wa.point=n,Gt(t=r,e=i)}}function Qt(){Wa.point=Gt}function ne(){function n(n,t){var e=n-r,u=t-i,o=Math.sqrt(e*e+u*u);qa+=o*(r+n)/2,Ta+=o*(i+t)/2,Ra+=o,o=i*n-r*t,Da+=o*(r+n),Pa+=o*(i+t),Ua+=3*o,Gt(r=n,i=t)}var t,e,r,i;Wa.point=function(u,o){Wa.point=n,Gt(t=r=u,e=i=o)},Wa.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,Ho)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function i(){a.point=t}function u(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:i,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=i,a.point=t},pointRadius:function(n){return o=n,a},result:b};return a}function ee(n){function t(n){return(a?r:e)(n)}function e(t){return ue(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=u,t.lineStart()}function u(e,r){var u=dt([e,r]),o=n(e,r);i(M,x,m,b,_,w,M=o[0],x=o[1],m=e,b=u[0],_=u[1],w=u[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function l(){ -r(),S.point=c,S.lineEnd=f}function c(n,t){u(s=n,h=t),p=M,g=x,v=b,d=_,y=w,S.point=u}function f(){i(M,x,m,b,_,w,p,g,s,v,d,y,a,t),S.lineEnd=o,o()}var s,h,p,g,v,d,y,m,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function i(t,e,r,a,l,c,f,s,h,p,g,v,d,y){var m=f-t,M=s-e,x=m*m+M*M;if(x>4*u&&d--){var b=a+p,_=l+g,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=xo(xo(w)-1)u||xo((m*z+M*L)/x-.5)>.3||o>a*p+l*g+c*v)&&(i(t,e,r,a,l,c,A,C,N,b/=S,_/=S,w,d,y),y.point(A,C),i(A,C,N,b,_,w,f,s,h,p,g,v,d,y))}}var u=.5,o=Math.cos(30*Yo),a=16;return t.precision=function(n){return arguments.length?(a=(u=n*n)>0&&16,t):Math.sqrt(u)},t}function re(n){var t=ee(function(t,e){return n([t*Zo,e*Zo])});return function(n){return le(t(n))}}function ie(n){this.stream=n}function ue(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function oe(n){return ae(function(){return n})()}function ae(n){function t(n){return n=a(n[0]*Yo,n[1]*Yo),[n[0]*h+l,c-n[1]*h]}function e(n){return n=a.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Zo,n[1]*Zo]}function r(){a=Ct(o=se(y,M,x),u);var n=u(v,d);return l=p-n[0]*h,c=g+n[1]*h,i()}function i(){return f&&(f.valid=!1,f=null),t}var u,o,a,l,c,f,s=ee(function(n,t){return n=u(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,p=480,g=250,v=0,d=0,y=0,M=0,x=0,b=Fa,_=m,w=null,S=null;return t.stream=function(n){return f&&(f.valid=!1),f=le(b(o,s(_(n)))),f.valid=!0,f},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Fa):It((w=+n)*Yo),i()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):m,i()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(p=+n[0],g=+n[1],r()):[p,g]},t.center=function(n){return arguments.length?(v=n[0]%360*Yo,d=n[1]%360*Yo,r()):[v*Zo,d*Zo]},t.rotate=function(n){return arguments.length?(y=n[0]%360*Yo,M=n[1]%360*Yo,x=n.length>2?n[2]%360*Yo:0,r()):[y*Zo,M*Zo,x*Zo]},ao.rebind(t,s,"precision"),function(){return u=n.apply(this,arguments),t.invert=u.invert&&e,r()}}function le(n){return ue(n,function(t,e){n.point(t*Yo,e*Yo)})}function ce(n,t){return[n,t]}function fe(n,t){return[n>Fo?n-Ho:-Fo>n?n+Ho:n,t]}function se(n,t,e){return n?t||e?Ct(pe(n),ge(t,e)):pe(n):t||e?ge(t,e):fe}function he(n){return function(t,e){return t+=n,[t>Fo?t-Ho:-Fo>t?t+Ho:t,e]}}function pe(n){var t=he(n);return t.invert=he(-n),t}function ge(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*r+a*i;return[Math.atan2(l*u-f*o,a*r-c*i),tn(f*u+l*o)]}var r=Math.cos(n),i=Math.sin(n),u=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*u-l*o;return[Math.atan2(l*u+c*o,a*r+f*i),tn(f*r-a*i)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(i,u,o,a){var l=o*t;null!=i?(i=de(e,i),u=de(e,u),(o>0?u>i:i>u)&&(i+=o*Ho)):(i=n+o*Ho,u=n-.5*l);for(var c,f=i;o>0?f>u:u>f;f-=l)a.point((c=_t([e,-r*Math.cos(f),-r*Math.sin(f)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Uo)%(2*Math.PI)}function ye(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function me(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var i=Math.cos(t),u=Math.sin(t),o=Math.cos(r),a=Math.sin(r),l=i*Math.cos(n),c=i*Math.sin(n),f=o*Math.cos(e),s=o*Math.sin(e),h=2*Math.asin(Math.sqrt(on(r-t)+i*o*on(e-n))),p=1/Math.sin(h),g=h?function(n){var t=Math.sin(n*=h)*p,e=Math.sin(h-n)*p,r=e*l+t*f,i=e*c+t*s,o=e*u+t*a;return[Math.atan2(i,r)*Zo,Math.atan2(o,Math.sqrt(r*r+i*i))*Zo]}:function(){return[n*Zo,t*Zo]};return g.distance=h,g}function _e(){function n(n,i){var u=Math.sin(i*=Yo),o=Math.cos(i),a=xo((n*=Yo)-t),l=Math.cos(a);Ja+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*u-e*o*l)*a),e*u+r*o*l),t=n,e=u,r=o}var t,e,r;Ga.point=function(i,u){t=i*Yo,e=Math.sin(u*=Yo),r=Math.cos(u),Ga.point=n},Ga.lineEnd=function(){Ga.point=Ga.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),i=Math.cos(e),u=n(r*i);return[u*i*Math.sin(t),u*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),i=t(r),u=Math.sin(i),o=Math.cos(i);return[Math.atan2(n*u,r*o),Math.asin(r&&e*u/r)]},e}function Se(n,t){function e(n,t){o>0?-Io+Uo>t&&(t=-Io+Uo):t>Io-Uo&&(t=Io-Uo);var e=o/Math.pow(i(t),u);return[e*Math.sin(u*n),o-e*Math.cos(u*n)]}var r=Math.cos(n),i=function(n){return Math.tan(Fo/4+n/2)},u=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(i(t)/i(n)),o=r*Math.pow(i(n),u)/u;return u?(e.invert=function(n,t){var e=o-t,r=K(u)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/u,2*Math.atan(Math.pow(o/r,1/u))-Io]},e):Ne}function ke(n,t){function e(n,t){var e=u-t;return[e*Math.sin(i*n),u-e*Math.cos(i*n)]}var r=Math.cos(n),i=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),u=r/i+n;return xo(i)i;i++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var i=n[0],u=e[0],o=t[0]-i,a=r[0]-u,l=n[1],c=e[1],f=t[1]-l,s=r[1]-c,h=(a*(l-c)-s*(i-u))/(s*o-a*f);return[i+h*o,l+h*f]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function Ue(n){var t=cl.pop()||new Pe;return t.site=n,t}function je(n){Be(n),ol.remove(n),cl.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,i={x:e,y:r},u=n.P,o=n.N,a=[n];je(n);for(var l=u;l.circle&&xo(e-l.circle.x)f;++f)c=a[f],l=a[f-1],nr(c.edge,l.site,c.site,i);l=a[0],c=a[s-1],c.edge=Ke(l.site,c.site,null,i),$e(l),$e(c)}function He(n){for(var t,e,r,i,u=n.x,o=n.y,a=ol._;a;)if(r=Oe(a,o)-u,r>Uo)a=a.L;else{if(i=u-Ie(a,o),!(i>Uo)){r>-Uo?(t=a.P,e=a):i>-Uo?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var l=Ue(n);if(ol.insert(t,l),t||e){if(t===e)return Be(t),e=Ue(t.site),ol.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,f=c.x,s=c.y,h=n.x-f,p=n.y-s,g=e.site,v=g.x-f,d=g.y-s,y=2*(h*d-p*v),m=h*h+p*p,M=v*v+d*d,x={x:(d*m-p*M)/y+f,y:(h*M-v*m)/y+s};nr(e.edge,c,g,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,g,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,i=e.y,u=i-t;if(!u)return r;var o=n.P;if(!o)return-(1/0);e=o.site;var a=e.x,l=e.y,c=l-t;if(!c)return a;var f=a-r,s=1/u-1/c,h=f/c;return s?(-h+Math.sqrt(h*h-2*s*(f*f/(-2*c)-l+c/2+i-u/2)))/s+r:(r+a)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Ze(n){for(var t,e,r,i,u,o,a,l,c,f,s=n[0][0],h=n[1][0],p=n[0][1],g=n[1][1],v=ul,d=v.length;d--;)if(u=v[d],u&&u.prepare())for(a=u.edges,l=a.length,o=0;l>o;)f=a[o].end(),r=f.x,i=f.y,c=a[++o%l].start(),t=c.x,e=c.y,(xo(r-t)>Uo||xo(i-e)>Uo)&&(a.splice(o,0,new tr(Qe(u.site,f,xo(r-s)Uo?{x:s,y:xo(t-s)Uo?{x:xo(e-g)Uo?{x:h,y:xo(t-h)Uo?{x:xo(e-p)=-jo)){var p=l*l+c*c,g=f*f+s*s,v=(s*p-c*g)/h,d=(l*g-f*p)/h,s=d+a,y=fl.pop()||new Xe;y.arc=n,y.site=i,y.x=v+o,y.y=s+Math.sqrt(v*v+d*d),y.cy=s,n.circle=y;for(var m=null,M=ll._;M;)if(y.yd||d>=a)return;if(h>g){if(u){if(u.y>=c)return}else u={x:d,y:l};e={x:d,y:c}}else{if(u){if(u.yr||r>1)if(h>g){if(u){if(u.y>=c)return}else u={x:(l-i)/r,y:l};e={x:(c-i)/r,y:c}}else{if(u){if(u.yp){if(u){if(u.x>=a)return}else u={x:o,y:r*o+i};e={x:a,y:r*a+i}}else{if(u){if(u.xu||s>o||r>h||i>p)){if(g=n.point){var g,v=t-n.x,d=e-n.y,y=v*v+d*d;if(l>y){var m=Math.sqrt(l=y);r=t-m,i=e-m,u=t+m,o=e+m,a=g}}for(var M=n.nodes,x=.5*(f+h),b=.5*(s+p),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,f,s,x,b);break;case 1:c(n,x,s,h,b);break;case 2:c(n,f,b,x,p);break;case 3:c(n,x,b,h,p)}}}(n,r,i,u,o),a}function vr(n,t){n=ao.rgb(n),t=ao.rgb(t);var e=n.r,r=n.g,i=n.b,u=t.r-e,o=t.g-r,a=t.b-i;return function(n){return"#"+bn(Math.round(e+u*n))+bn(Math.round(r+o*n))+bn(Math.round(i+a*n))}}function dr(n,t){var e,r={},i={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):i[e]=n[e];for(e in t)e in n||(i[e]=t[e]);return function(n){for(e in r)i[e]=r[e](n);return i}}function yr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function mr(n,t){var e,r,i,u=hl.lastIndex=pl.lastIndex=0,o=-1,a=[],l=[];for(n+="",t+="";(e=hl.exec(n))&&(r=pl.exec(t));)(i=r.index)>u&&(i=t.slice(u,i),a[o]?a[o]+=i:a[++o]=i),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,l.push({i:o,x:yr(e,r)})),u=pl.lastIndex;return ur;++r)a[(e=l[r]).i]=e.x(n);return a.join("")})}function Mr(n,t){for(var e,r=ao.interpolators.length;--r>=0&&!(e=ao.interpolators[r](n,t)););return e}function xr(n,t){var e,r=[],i=[],u=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(Mr(n[e],t[e]));for(;u>e;++e)i[e]=n[e];for(;o>e;++e)i[e]=t[e];return function(n){for(e=0;a>e;++e)i[e]=r[e](n);return i}}function br(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function _r(n){return function(t){return 1-n(1-t)}}function wr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Sr(n){return n*n}function kr(n){return n*n*n}function Nr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Er(n){return function(t){return Math.pow(t,n)}}function Ar(n){return 1-Math.cos(n*Io)}function Cr(n){return Math.pow(2,10*(n-1))}function zr(n){return 1-Math.sqrt(1-n*n)}function Lr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Ho*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Ho/t)}}function qr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Tr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Rr(n,t){n=ao.hcl(n),t=ao.hcl(t);var e=n.h,r=n.c,i=n.l,u=t.h-e,o=t.c-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return sn(e+u*n,r+o*n,i+a*n)+""}}function Dr(n,t){n=ao.hsl(n),t=ao.hsl(t);var e=n.h,r=n.s,i=n.l,u=t.h-e,o=t.s-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return cn(e+u*n,r+o*n,i+a*n)+""}}function Pr(n,t){n=ao.lab(n),t=ao.lab(t);var e=n.l,r=n.a,i=n.b,u=t.l-e,o=t.a-r,a=t.b-i;return function(n){return pn(e+u*n,r+o*n,i+a*n)+""}}function Ur(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function jr(n){var t=[n.a,n.b],e=[n.c,n.d],r=Hr(t),i=Fr(t,e),u=Hr(Or(e,t,-i))||0;t[0]*e[1]180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:yr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:yr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:i-4,x:yr(n[0],t[0])},{i:i-2,x:yr(n[1],t[1])})}else 1===t[0]&&1===t[1]||e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=ao.transform(n),t=ao.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.rotate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,i=-1,u=r.length;++i=0;)e.push(i[r])}function oi(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(u=n.children)&&(i=u.length))for(var i,u,o=-1;++oe;++e)(t=n[e][1])>i&&(r=e,i=t);return r}function yi(n){return n.reduce(mi,0)}function mi(n,t){return n+t[1]}function Mi(n,t){return xi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xi(n,t){for(var e=-1,r=+n[0],i=(n[1]-r)/t,u=[];++e<=t;)u[e]=i*e+r;return u}function bi(n){return[ao.min(n),ao.max(n)]}function _i(n,t){return n.value-t.value}function wi(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Si(n,t){n._pack_next=t,t._pack_prev=n}function ki(n,t){var e=t.x-n.x,r=t.y-n.y,i=n.r+t.r;return.999*i*i>e*e+r*r}function Ni(n){function t(n){f=Math.min(n.x-n.r,f),s=Math.max(n.x+n.r,s),h=Math.min(n.y-n.r,h),p=Math.max(n.y+n.r,p)}if((e=n.children)&&(c=e.length)){var e,r,i,u,o,a,l,c,f=1/0,s=-(1/0),h=1/0,p=-(1/0);if(e.forEach(Ei),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(i=e[1],i.x=i.r,i.y=0,t(i),c>2))for(u=e[2],zi(r,i,u),t(u),wi(r,u),r._pack_prev=u,wi(u,i),i=r._pack_next,o=3;c>o;o++){zi(r,i,u=e[o]);var g=0,v=1,d=1;for(a=i._pack_next;a!==i;a=a._pack_next,v++)if(ki(a,u)){g=1;break}if(1==g)for(l=r._pack_prev;l!==a._pack_prev&&!ki(l,u);l=l._pack_prev,d++);g?(d>v||v==d&&i.ro;o++)u=e[o],u.x-=y,u.y-=m,M=Math.max(M,u.r+Math.sqrt(u.x*u.x+u.y*u.y));n.r=M,e.forEach(Ai)}}function Ei(n){n._pack_next=n._pack_prev=n}function Ai(n){delete n._pack_next,delete n._pack_prev}function Ci(n,t,e,r){var i=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,i)for(var u=-1,o=i.length;++u=0;)t=i[u],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pi(n,t,e){return n.a.parent===t.parent?n.a:e}function Ui(n){return 1+ao.max(n,function(n){return n.y})}function ji(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fi(n){var t=n.children;return t&&t.length?Fi(t[0]):n}function Hi(n){var t,e=n.children;return e&&(t=e.length)?Hi(e[t-1]):n}function Oi(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Ii(n,t){var e=n.x+t[3],r=n.y+t[0],i=n.dx-t[1]-t[3],u=n.dy-t[0]-t[2];return 0>i&&(e+=i/2,i=0),0>u&&(r+=u/2,u=0),{x:e,y:r,dx:i,dy:u}}function Yi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zi(n){return n.rangeExtent?n.rangeExtent():Yi(n.range())}function Vi(n,t,e,r){var i=e(n[0],n[1]),u=r(t[0],t[1]);return function(n){return u(i(n))}}function Xi(n,t){var e,r=0,i=n.length-1,u=n[r],o=n[i];return u>o&&(e=r,r=i,i=e,e=u,u=o,o=e),n[r]=t.floor(u),n[i]=t.ceil(o),n}function $i(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:Sl}function Bi(n,t,e,r){var i=[],u=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?Bi:Vi,l=r?Wr:Br;return o=i(n,t,l,e),a=i(t,n,l,Mr),u}function u(n){return o(n)}var o,a;return u.invert=function(n){return a(n)},u.domain=function(t){return arguments.length?(n=t.map(Number),i()):n},u.range=function(n){return arguments.length?(t=n,i()):t},u.rangeRound=function(n){return u.range(n).interpolate(Ur)},u.clamp=function(n){return arguments.length?(r=n,i()):r},u.interpolate=function(n){return arguments.length?(e=n,i()):e},u.ticks=function(t){return Qi(n,t)},u.tickFormat=function(t,e){return nu(n,t,e)},u.nice=function(t){return Gi(n,t),i()},u.copy=function(){return Wi(n,t,e,r)},i()}function Ji(n,t){return ao.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gi(n,t){return Xi(n,$i(Ki(n,t)[2])),Xi(n,$i(Ki(n,t)[2])),n}function Ki(n,t){null==t&&(t=10);var e=Yi(n),r=e[1]-e[0],i=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),u=t/r*i;return.15>=u?i*=10:.35>=u?i*=5:.75>=u&&(i*=2),e[0]=Math.ceil(e[0]/i)*i,e[1]=Math.floor(e[1]/i)*i+.5*i,e[2]=i,e}function Qi(n,t){return ao.range.apply(ao,Ki(n,t))}function nu(n,t,e){var r=Ki(n,t);if(e){var i=ha.exec(e);if(i.shift(),"s"===i[8]){var u=ao.formatPrefix(Math.max(xo(r[0]),xo(r[1])));return i[7]||(i[7]="."+tu(u.scale(r[2]))),i[8]="f",e=ao.format(i.join("")),function(n){return e(u.scale(n))+u.symbol}}i[7]||(i[7]="."+eu(i[8],r)),e=i.join("")}else e=",."+tu(r[2])+"f";return ao.format(e)}function tu(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function eu(n,t){var e=tu(t[2]);return n in kl?Math.abs(e-tu(Math.max(xo(t[0]),xo(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ru(n,t,e,r){function i(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function u(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(i(t))}return o.invert=function(t){return u(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(i)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(i)),o):t},o.nice=function(){var t=Xi(r.map(i),e?Math:El);return n.domain(t),r=t.map(u),o},o.ticks=function(){var n=Yi(r),o=[],a=n[0],l=n[1],c=Math.floor(i(a)),f=Math.ceil(i(l)),s=t%1?2:t;if(isFinite(f-c)){if(e){for(;f>c;c++)for(var h=1;s>h;h++)o.push(u(c)*h);o.push(u(c))}else for(o.push(u(c));c++0;h--)o.push(u(c)*h);for(c=0;o[c]l;f--);o=o.slice(c,f)}return o},o.tickFormat=function(n,e){if(!arguments.length)return Nl;arguments.length<2?e=Nl:"function"!=typeof e&&(e=ao.format(e));var r=Math.max(1,t*n/o.ticks().length);return function(n){var o=n/u(Math.round(i(n)));return t-.5>o*t&&(o*=t),r>=o?e(n):""}},o.copy=function(){return ru(n.copy(),t,e,r)},Ji(o,n)}function iu(n,t,e){function r(t){return n(i(t))}var i=uu(t),u=uu(1/t);return r.invert=function(t){return u(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(i)),r):e},r.ticks=function(n){return Qi(e,n)},r.tickFormat=function(n,t){return nu(e,n,t)},r.nice=function(n){return r.domain(Gi(e,n))},r.exponent=function(o){return arguments.length?(i=uu(t=o),u=uu(1/t),n.domain(e.map(i)),r):t},r.copy=function(){return iu(n.copy(),t,e)},Ji(r,n)}function uu(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ou(n,t){function e(e){return u[((i.get(e)||("range"===t.t?i.set(e,n.push(e)):NaN))-1)%u.length]}function r(t,e){return ao.range(n.length).map(function(n){return t+e*n})}var i,u,o;return e.domain=function(r){if(!arguments.length)return n;n=[],i=new c;for(var u,o=-1,a=r.length;++oe?[NaN,NaN]:[e>0?a[e-1]:n[0],et?NaN:t/u+n,[t,t+1/u]},r.copy=function(){return lu(n,t,e)},i()}function cu(n,t){function e(e){return e>=e?t[ao.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return cu(n,t)},e}function fu(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qi(n,t)},t.tickFormat=function(t,e){return nu(n,t,e)},t.copy=function(){return fu(n)},t}function su(){return 0}function hu(n){return n.innerRadius}function pu(n){return n.outerRadius}function gu(n){return n.startAngle}function vu(n){return n.endAngle}function du(n){return n&&n.padAngle}function yu(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function mu(n,t,e,r,i){var u=n[0]-t[0],o=n[1]-t[1],a=(i?r:-r)/Math.sqrt(u*u+o*o),l=a*o,c=-a*u,f=n[0]+l,s=n[1]+c,h=t[0]+l,p=t[1]+c,g=(f+h)/2,v=(s+p)/2,d=h-f,y=p-s,m=d*d+y*y,M=e-r,x=f*p-h*s,b=(0>y?-1:1)*Math.sqrt(Math.max(0,M*M*m-x*x)),_=(x*y-d*b)/m,w=(-x*d-y*b)/m,S=(x*y+d*b)/m,k=(-x*d+y*b)/m,N=_-g,E=w-v,A=S-g,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mu(n){function t(t){function o(){c.push("M",u(n(f),a))}for(var l,c=[],f=[],s=-1,h=t.length,p=En(e),g=En(r);++s1?n.join("L"):n+"Z"}function bu(n){return n.join("L")+"Z"}function _u(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1&&i.push("H",r[0]),i.join("")}function wu(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1){a=t[1],u=n[l],l++,r+="C"+(i[0]+o[0])+","+(i[1]+o[1])+","+(u[0]-a[0])+","+(u[1]-a[1])+","+u[0]+","+u[1];for(var c=2;c9&&(i=3*t/Math.sqrt(i),o[a]=i*e,o[a+1]=i*r));for(a=-1;++a<=l;)i=(n[Math.min(l,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),u.push([i||0,o[a]*i||0]);return u}function Fu(n){return n.length<3?xu(n):n[0]+Au(n,ju(n))}function Hu(n){for(var t,e,r,i=-1,u=n.length;++i=t?o(n-t):void(f.c=o)}function o(e){var i=g.active,u=g[i];u&&(u.timer.c=null,u.timer.t=NaN,--g.count,delete g[i],u.event&&u.event.interrupt.call(n,n.__data__,u.index));for(var o in g)if(r>+o){var c=g[o];c.timer.c=null,c.timer.t=NaN,--g.count,delete g[o]}f.c=a,qn(function(){return f.c&&a(e||1)&&(f.c=null,f.t=NaN),1},0,l),g.active=r,v.event&&v.event.start.call(n,n.__data__,t),p=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&p.push(r)}),h=v.ease,s=v.duration}function a(i){for(var u=i/s,o=h(u),a=p.length;a>0;)p[--a].call(n,o);return u>=1?(v.event&&v.event.end.call(n,n.__data__,t),--g.count?delete g[r]:delete n[e],1):void 0}var l,f,s,h,p,g=n[e]||(n[e]={active:0,count:0}),v=g[r];v||(l=i.time,f=qn(u,0,l),v=g[r]={tween:new c,time:l,timer:f,delay:i.delay,duration:i.duration,ease:i.ease,index:t},i=null,++g.count)}function no(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function to(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function eo(n){return n.toISOString()}function ro(n,t,e){function r(t){return n(t)}function i(n,e){var r=n[1]-n[0],i=r/e,u=ao.bisect(Kl,i);return u==Kl.length?[t.year,Ki(n.map(function(n){return n/31536e6}),e)[2]]:u?t[i/Kl[u-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=io(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=io(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yi(r.domain()),u=null==n?i(e,10):"number"==typeof n?i(e,n):!n.range&&[{range:n},t];return u&&(n=u[0],t=u[1]),n.range(e[0],io(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ro(n.copy(),t,e)},Ji(r,n)}function io(n){return new Date(n)}function uo(n){return JSON.parse(n.responseText)}function oo(n){var t=fo.createRange();return t.selectNode(fo.body),t.createContextualFragment(n.responseText)}var ao={version:"3.5.17"},lo=[].slice,co=function(n){return lo.call(n)},fo=this.document;if(fo)try{co(fo.documentElement.childNodes)[0].nodeType}catch(so){co=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),fo)try{fo.createElement("DIV").style.setProperty("opacity",0,"")}catch(ho){var po=this.Element.prototype,go=po.setAttribute,vo=po.setAttributeNS,yo=this.CSSStyleDeclaration.prototype,mo=yo.setProperty;po.setAttribute=function(n,t){go.call(this,n,t+"")},po.setAttributeNS=function(n,t,e){vo.call(this,n,t,e+"")},yo.setProperty=function(n,t,e){mo.call(this,n,t+"",e)}}ao.ascending=e,ao.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},ao.min=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ir&&(e=r)}else{for(;++i=r){e=r;break}for(;++ir&&(e=r)}return e},ao.max=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ie&&(e=r)}else{for(;++i=r){e=r;break}for(;++ie&&(e=r)}return e},ao.extent=function(n,t){var e,r,i,u=-1,o=n.length;if(1===arguments.length){for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}else{for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}return[e,i]},ao.sum=function(n,t){var e,r=0,u=n.length,o=-1;if(1===arguments.length)for(;++o1?l/(f-1):void 0},ao.deviation=function(){var n=ao.variance.apply(this,arguments);return n?Math.sqrt(n):n};var Mo=u(e);ao.bisectLeft=Mo.left,ao.bisect=ao.bisectRight=Mo.right,ao.bisector=function(n){return u(1===n.length?function(t,r){return e(n(t),r)}:n)},ao.shuffle=function(n,t,e){(u=arguments.length)<3&&(e=n.length,2>u&&(t=0));for(var r,i,u=e-t;u;)i=Math.random()*u--|0,r=n[u+t],n[u+t]=n[i+t],n[i+t]=r;return n},ao.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ao.pairs=function(n){for(var t,e=0,r=n.length-1,i=n[0],u=new Array(0>r?0:r);r>e;)u[e]=[t=i,i=n[++e]];return u},ao.transpose=function(n){if(!(i=n.length))return[];for(var t=-1,e=ao.min(n,o),r=new Array(e);++t=0;)for(r=n[i],t=r.length;--t>=0;)e[--o]=r[t];return e};var xo=Math.abs;ao.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,i=[],u=a(xo(e)),o=-1;if(n*=u,t*=u,e*=u,0>e)for(;(r=n+e*++o)>t;)i.push(r/u);else for(;(r=n+e*++o)=u.length)return r?r.call(i,o):e?o.sort(e):o;for(var l,f,s,h,p=-1,g=o.length,v=u[a++],d=new c;++p=u.length)return n;var r=[],i=o[e++];return n.forEach(function(n,i){r.push({key:n,values:t(i,e)})}),i?r.sort(function(n,t){return i(n.key,t.key)}):r}var e,r,i={},u=[],o=[];return i.map=function(t,e){return n(e,t,0)},i.entries=function(e){return t(n(ao.map,e,0),0)},i.key=function(n){return u.push(n),i},i.sortKeys=function(n){return o[u.length-1]=n,i},i.sortValues=function(n){return e=n,i},i.rollup=function(n){return r=n,i},i},ao.set=function(n){var t=new y;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(y,{has:h,add:function(n){return this._[f(n+="")]=!0,n},remove:p,values:g,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,s(t))}}),ao.behavior={},ao.rebind=function(n,t){for(var e,r=1,i=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ao.event=null,ao.requote=function(n){return n.replace(So,"\\$&")};var So=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,ko={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},No=function(n,t){return t.querySelector(n)},Eo=function(n,t){return t.querySelectorAll(n)},Ao=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ao=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(No=function(n,t){return Sizzle(n,t)[0]||null},Eo=Sizzle,Ao=Sizzle.matchesSelector),ao.selection=function(){return ao.select(fo.documentElement)};var Co=ao.selection.prototype=[];Co.select=function(n){var t,e,r,i,u=[];n=A(n);for(var o=-1,a=this.length;++o=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),Lo.hasOwnProperty(e)?{space:Lo[e],local:n}:n}},Co.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ao.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Co.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,i=-1;if(t=e.classList){for(;++ii){if("string"!=typeof n){2>i&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>i){var u=this.node();return t(u).getComputedStyle(u,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Co.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},Co.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Co.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Co.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Co.insert=function(n,t){return n=j(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Co.remove=function(){return this.each(F)},Co.data=function(n,t){function e(n,e){var r,i,u,o=n.length,s=e.length,h=Math.min(o,s),p=new Array(s),g=new Array(s),v=new Array(o);if(t){var d,y=new c,m=new Array(o);for(r=-1;++rr;++r)g[r]=H(e[r]);for(;o>r;++r)v[r]=n[r]}g.update=p,g.parentNode=p.parentNode=v.parentNode=n.parentNode,a.push(g),l.push(p),f.push(v)}var r,i,u=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++uu;u++){i.push(t=[]),t.parentNode=(e=this[u]).parentNode;for(var a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return E(i)},Co.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[i])&&(u&&u!==e.nextSibling&&u.parentNode.insertBefore(e,u),u=e);return this},Co.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,i=e.length;i>r;r++){var u=e[r];if(u)return u}return null},Co.size=function(){var n=0;return Y(this,function(){++n}),n};var qo=[];ao.selection.enter=Z,ao.selection.enter.prototype=qo,qo.append=Co.append,qo.empty=Co.empty,qo.node=Co.node,qo.call=Co.call,qo.size=Co.size,qo.select=function(n){for(var t,e,r,i,u,o=[],a=-1,l=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var To=ao.map({mouseenter:"mouseover",mouseleave:"mouseout"});fo&&To.forEach(function(n){"on"+n in fo&&To.remove(n)});var Ro,Do=0;ao.mouse=function(n){return J(n,k())};var Po=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;ao.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,i=0,u=t.length;u>i;++i)if((r=t[i]).identifier===e)return J(n,r)},ao.behavior.drag=function(){function n(){this.on("mousedown.drag",u).on("touchstart.drag",o)}function e(n,t,e,u,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],g|=n|e,M=r,p({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(y.on(u+d,null).on(o+d,null),m(g),p({type:"dragend"}))}var c,f=this,s=ao.event.target.correspondingElement||ao.event.target,h=f.parentNode,p=r.of(f,arguments),g=0,v=n(),d=".drag"+(null==v?"":"-"+v),y=ao.select(e(s)).on(u+d,a).on(o+d,l),m=W(s),M=t(h,v);i?(c=i.apply(f,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],p({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),i=null,u=e(b,ao.mouse,t,"mousemove","mouseup"),o=e(G,ao.touch,m,"touchmove","touchend");return n.origin=function(t){return arguments.length?(i=t,n):i},ao.rebind(n,r,"on")},ao.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?co(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Uo=1e-6,jo=Uo*Uo,Fo=Math.PI,Ho=2*Fo,Oo=Ho-Uo,Io=Fo/2,Yo=Fo/180,Zo=180/Fo,Vo=Math.SQRT2,Xo=2,$o=4;ao.interpolateZoom=function(n,t){var e,r,i=n[0],u=n[1],o=n[2],a=t[0],l=t[1],c=t[2],f=a-i,s=l-u,h=f*f+s*s;if(jo>h)r=Math.log(c/o)/Vo,e=function(n){return[i+n*f,u+n*s,o*Math.exp(Vo*n*r)]};else{var p=Math.sqrt(h),g=(c*c-o*o+$o*h)/(2*o*Xo*p),v=(c*c-o*o-$o*h)/(2*c*Xo*p),d=Math.log(Math.sqrt(g*g+1)-g),y=Math.log(Math.sqrt(v*v+1)-v);r=(y-d)/Vo,e=function(n){var t=n*r,e=rn(d),a=o/(Xo*p)*(e*un(Vo*t+d)-en(d));return[i+a*f,u+a*s,o*e/rn(Vo*t+d)]}}return e.duration=1e3*r,e},ao.behavior.zoom=function(){function n(n){n.on(L,s).on(Wo+".zoom",p).on("dblclick.zoom",g).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function i(n){k.k=Math.max(A[0],Math.min(A[1],n))}function u(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function o(t,e,r,o){t.__chart__={x:k.x,y:k.y,k:k.k},i(Math.pow(2,o)),u(d=e,r),t=ao.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function a(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){a(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function f(n){--z||(n({type:"zoomend"}),d=null)}function s(){function n(){a=1,u(ao.mouse(i),h),c(o)}function r(){s.on(q,null).on(T,null),p(a),f(o)}var i=this,o=D.of(i,arguments),a=0,s=ao.select(t(i)).on(q,n).on(T,r),h=e(ao.mouse(i)),p=W(i);Il.call(i),l(o)}function h(){function n(){var n=ao.touches(g);return p=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=ao.event.target;ao.select(t).on(x,r).on(b,a),_.push(t);for(var e=ao.event.changedTouches,i=0,u=e.length;u>i;++i)d[e[i].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var f=l[0];o(g,f,d[f.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var f=l[0],s=l[1],h=f[0]-s[0],p=f[1]-s[1];y=h*h+p*p}}function r(){var n,t,e,r,o=ao.touches(g);Il.call(g);for(var a=0,l=o.length;l>a;++a,r=null)if(e=o[a],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var f=(f=e[0]-n[0])*f+(f=e[1]-n[1])*f,s=y&&Math.sqrt(f/y);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],i(s*p)}M=null,u(n,t),c(v)}function a(){if(ao.event.touches.length){for(var t=ao.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var i in d)return void n()}ao.selectAll(_).on(m,null),w.on(L,s).on(R,h),N(),f(v)}var p,g=this,v=D.of(g,arguments),d={},y=0,m=".zoom-"+ao.event.changedTouches[0].identifier,x="touchmove"+m,b="touchend"+m,_=[],w=ao.select(g),N=W(g);t(),l(v),w.on(L,null).on(R,t)}function p(){var n=D.of(this,arguments);m?clearTimeout(m):(Il.call(this),v=e(d=y||ao.mouse(this)),l(n)),m=setTimeout(function(){m=null,f(n)},50),S(),i(Math.pow(2,.002*Bo())*k.k),u(d,v),c(n)}function g(){var n=ao.mouse(this),t=Math.log(k.k)/Math.LN2;o(this,n,e(n),ao.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,y,m,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Jo,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=N(n,"zoomstart","zoom","zoomend");return Wo||(Wo="onwheel"in fo?(Bo=function(){return-ao.event.deltaY*(ao.event.deltaMode?120:1)},"wheel"):"onmousewheel"in fo?(Bo=function(){return ao.event.wheelDelta},"mousewheel"):(Bo=function(){return-ao.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Hl?ao.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],i=d?d[0]:e/2,u=d?d[1]:r/2,o=ao.interpolateZoom([(i-k.x)/k.k,(u-k.y)/k.k,e/k.k],[(i-t.x)/t.k,(u-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:i-r[0]*a,y:u-r[1]*a,k:a},c(n)}}).each("interrupt.zoom",function(){f(n)}).each("end.zoom",function(){f(n)}):(this.__chart__=k,l(n),c(n),f(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},a(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},i(+t),a(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Jo:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(y=t&&[+t[0],+t[1]],n):y},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},ao.rebind(n,D,"on")};var Bo,Wo,Jo=[0,1/0];ao.color=an,an.prototype.toString=function(){return this.rgb()+""},ao.hsl=ln;var Go=ln.prototype=new an;Go.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Go.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Go.rgb=function(){return cn(this.h,this.s,this.l)},ao.hcl=fn;var Ko=fn.prototype=new an;Ko.brighter=function(n){return new fn(this.h,this.c,Math.min(100,this.l+Qo*(arguments.length?n:1)))},Ko.darker=function(n){return new fn(this.h,this.c,Math.max(0,this.l-Qo*(arguments.length?n:1)))},Ko.rgb=function(){return sn(this.h,this.c,this.l).rgb()},ao.lab=hn;var Qo=18,na=.95047,ta=1,ea=1.08883,ra=hn.prototype=new an;ra.brighter=function(n){return new hn(Math.min(100,this.l+Qo*(arguments.length?n:1)),this.a,this.b)},ra.darker=function(n){return new hn(Math.max(0,this.l-Qo*(arguments.length?n:1)),this.a,this.b)},ra.rgb=function(){return pn(this.l,this.a,this.b)},ao.rgb=mn;var ia=mn.prototype=new an;ia.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,i=30;return t||e||r?(t&&i>t&&(t=i),e&&i>e&&(e=i),r&&i>r&&(r=i),new mn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new mn(i,i,i)},ia.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new mn(n*this.r,n*this.g,n*this.b)},ia.hsl=function(){return wn(this.r,this.g,this.b)},ia.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var ua=ao.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});ua.forEach(function(n,t){ua.set(n,Mn(t))}),ao.functor=En,ao.xhr=An(m),ao.dsv=function(n,t){function e(n,e,u){arguments.length<3&&(u=e,e=null);var o=Cn(n,t,null==e?r:i(e),u);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:i(n)):e},o}function r(n){return e.parse(n.responseText)}function i(n){return function(t){return e.parse(t.responseText,n)}}function u(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var i=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(i(n),e)}:i})},e.parseRows=function(n,t){function e(){if(f>=c)return o;if(i)return i=!1,u;var t=f;if(34===n.charCodeAt(t)){for(var e=t;e++f;){var r=n.charCodeAt(f++),a=1;if(10===r)i=!0;else if(13===r)i=!0,10===n.charCodeAt(f)&&(++f,++a);else if(r!==l)continue;return n.slice(t,f-a)}return n.slice(t)}for(var r,i,u={},o={},a=[],c=n.length,f=0,s=0;(r=e())!==o;){for(var h=[];r!==u&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,s++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new y,i=[];return t.forEach(function(n){for(var t in n)r.has(t)||i.push(r.add(t))}),[i.map(o).join(n)].concat(t.map(function(t){return i.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(u).join("\n")},e},ao.csv=ao.dsv(",","text/csv"),ao.tsv=ao.dsv(" ","text/tab-separated-values");var oa,aa,la,ca,fa=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ao.timer=function(){qn.apply(this,arguments)},ao.timer.flush=function(){Rn(),Dn()},ao.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var sa=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Un);ao.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=ao.round(n,Pn(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),sa[8+e/3]};var ha=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,pa=ao.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ao.round(n,Pn(n,t))).toFixed(Math.max(0,Math.min(20,Pn(n*(1+1e-15),t))))}}),ga=ao.time={},va=Date;Hn.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){da.setUTCDate.apply(this._,arguments)},setDay:function(){da.setUTCDay.apply(this._,arguments)},setFullYear:function(){da.setUTCFullYear.apply(this._,arguments)},setHours:function(){da.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){da.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){da.setUTCMinutes.apply(this._,arguments)},setMonth:function(){da.setUTCMonth.apply(this._,arguments)},setSeconds:function(){da.setUTCSeconds.apply(this._,arguments)},setTime:function(){da.setTime.apply(this._,arguments)}};var da=Date.prototype;ga.year=On(function(n){return n=ga.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ga.years=ga.year.range,ga.years.utc=ga.year.utc.range,ga.day=On(function(n){var t=new va(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ga.days=ga.day.range,ga.days.utc=ga.day.utc.range,ga.dayOfYear=function(n){var t=ga.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ga[n]=On(function(n){return(n=ga.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ga[n+"s"]=e.range,ga[n+"s"].utc=e.utc.range,ga[n+"OfYear"]=function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)}}),ga.week=ga.sunday,ga.weeks=ga.sunday.range,ga.weeks.utc=ga.sunday.utc.range,ga.weekOfYear=ga.sundayOfYear;var ya={"-":"",_:" ",0:"0"},ma=/^\s*\d+/,Ma=/^%/;ao.locale=function(n){return{numberFormat:jn(n),timeFormat:Yn(n)}};var xa=ao.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], -shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});ao.format=xa.numberFormat,ao.geo={},ft.prototype={s:0,t:0,add:function(n){st(n,this.t,ba),st(ba.s,this.s,this),this.s?this.t+=ba.t:this.s=ba.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var ba=new ft;ao.geo.stream=function(n,t){n&&_a.hasOwnProperty(n.type)?_a[n.type](n,t):ht(n,t)};var _a={Feature:function(n,t){ht(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,i=e.length;++rn?4*Fo+n:n,Na.lineStart=Na.lineEnd=Na.point=b}};ao.geo.bounds=function(){function n(n,t){M.push(x=[f=n,h=n]),s>t&&(s=t),t>p&&(p=t)}function t(t,e){var r=dt([t*Yo,e*Yo]);if(y){var i=mt(y,r),u=[i[1],-i[0],0],o=mt(u,i);bt(o),o=_t(o);var l=t-g,c=l>0?1:-1,v=o[0]*Zo*c,d=xo(l)>180;if(d^(v>c*g&&c*t>v)){var m=o[1]*Zo;m>p&&(p=m)}else if(v=(v+360)%360-180,d^(v>c*g&&c*t>v)){var m=-o[1]*Zo;s>m&&(s=m)}else s>e&&(s=e),e>p&&(p=e);d?g>t?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t):h>=f?(f>t&&(f=t),t>h&&(h=t)):t>g?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t)}else n(t,e);y=r,g=t}function e(){b.point=t}function r(){x[0]=f,x[1]=h,b.point=n,y=null}function i(n,e){if(y){var r=n-g;m+=xo(r)>180?r+(r>0?360:-360):r}else v=n,d=e;Na.point(n,e),t(n,e)}function u(){Na.lineStart()}function o(){i(v,d),Na.lineEnd(),xo(m)>Uo&&(f=-(h=180)),x[0]=f,x[1]=h,y=null}function a(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nka?(f=-(h=180),s=-(p=90)):m>Uo?p=90:-Uo>m&&(s=-90),x[0]=f,x[1]=h}};return function(n){p=h=-(f=s=1/0),M=[],ao.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,i=M[0],u=[i];t>r;++r)e=M[r],c(e[0],i)||c(e[1],i)?(a(i[0],e[1])>a(i[0],i[1])&&(i[1]=e[1]),a(e[0],i[1])>a(i[0],i[1])&&(i[0]=e[0])):u.push(i=e);for(var o,e,g=-(1/0),t=u.length-1,r=0,i=u[t];t>=r;i=e,++r)e=u[r],(o=a(i[1],e[0]))>g&&(g=o,f=e[0],h=i[1])}return M=x=null,f===1/0||s===1/0?[[NaN,NaN],[NaN,NaN]]:[[f,s],[h,p]]}}(),ao.geo.centroid=function(n){Ea=Aa=Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,ja);var t=Da,e=Pa,r=Ua,i=t*t+e*e+r*r;return jo>i&&(t=qa,e=Ta,r=Ra,Uo>Aa&&(t=Ca,e=za,r=La),i=t*t+e*e+r*r,jo>i)?[NaN,NaN]:[Math.atan2(e,t)*Zo,tn(r/Math.sqrt(i))*Zo]};var Ea,Aa,Ca,za,La,qa,Ta,Ra,Da,Pa,Ua,ja={sphere:b,point:St,lineStart:Nt,lineEnd:Et,polygonStart:function(){ja.lineStart=At},polygonEnd:function(){ja.lineStart=Nt}},Fa=Rt(zt,jt,Ht,[-Fo,-Fo/2]),Ha=1e9;ao.geo.clipExtent=function(){var n,t,e,r,i,u,o={stream:function(n){return i&&(i.valid=!1),i=u(n),i.valid=!0,i},extent:function(a){return arguments.length?(u=Zt(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),i&&(i.valid=!1,i=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(ao.geo.conicEqualArea=function(){return Vt(Xt)}).raw=Xt,ao.geo.albers=function(){return ao.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ao.geo.albersUsa=function(){function n(n){var u=n[0],o=n[1];return t=null,e(u,o),t||(r(u,o),t)||i(u,o),t}var t,e,r,i,u=ao.geo.albers(),o=ao.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=ao.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=u.scale(),e=u.translate(),r=(n[0]-e[0])/t,i=(n[1]-e[1])/t;return(i>=.12&&.234>i&&r>=-.425&&-.214>r?o:i>=.166&&.234>i&&r>=-.214&&-.115>r?a:u).invert(n)},n.stream=function(n){var t=u.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,i){t.point(n,i),e.point(n,i),r.point(n,i)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(u.precision(t),o.precision(t),a.precision(t),n):u.precision()},n.scale=function(t){return arguments.length?(u.scale(t),o.scale(.35*t),a.scale(t),n.translate(u.translate())):u.scale()},n.translate=function(t){if(!arguments.length)return u.translate();var c=u.scale(),f=+t[0],s=+t[1];return e=u.translate(t).clipExtent([[f-.455*c,s-.238*c],[f+.455*c,s+.238*c]]).stream(l).point,r=o.translate([f-.307*c,s+.201*c]).clipExtent([[f-.425*c+Uo,s+.12*c+Uo],[f-.214*c-Uo,s+.234*c-Uo]]).stream(l).point,i=a.translate([f-.205*c,s+.212*c]).clipExtent([[f-.214*c+Uo,s+.166*c+Uo],[f-.115*c-Uo,s+.234*c-Uo]]).stream(l).point,n},n.scale(1070)};var Oa,Ia,Ya,Za,Va,Xa,$a={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Ia=0,$a.lineStart=$t},polygonEnd:function(){$a.lineStart=$a.lineEnd=$a.point=b,Oa+=xo(Ia/2)}},Ba={point:Bt,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Wa={point:Gt,lineStart:Kt,lineEnd:Qt,polygonStart:function(){Wa.lineStart=ne},polygonEnd:function(){Wa.point=Gt,Wa.lineStart=Kt,Wa.lineEnd=Qt}};ao.geo.path=function(){function n(n){return n&&("function"==typeof a&&u.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=i(u)),ao.geo.stream(n,o)),u.result()}function t(){return o=null,n}var e,r,i,u,o,a=4.5;return n.area=function(n){return Oa=0,ao.geo.stream(n,i($a)),Oa},n.centroid=function(n){return Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,i(Wa)),Ua?[Da/Ua,Pa/Ua]:Ra?[qa/Ra,Ta/Ra]:La?[Ca/La,za/La]:[NaN,NaN]},n.bounds=function(n){return Va=Xa=-(Ya=Za=1/0),ao.geo.stream(n,i(Ba)),[[Ya,Za],[Va,Xa]]},n.projection=function(n){return arguments.length?(i=(e=n)?n.stream||re(n):m,t()):e},n.context=function(n){return arguments.length?(u=null==(r=n)?new Wt:new te(n),"function"!=typeof a&&u.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(u.pointRadius(+t),+t),n):a},n.projection(ao.geo.albersUsa()).context(null)},ao.geo.transform=function(n){return{stream:function(t){var e=new ie(t);for(var r in n)e[r]=n[r];return e}}},ie.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},ao.geo.projection=oe,ao.geo.projectionMutator=ae,(ao.geo.equirectangular=function(){return oe(ce)}).raw=ce.invert=ce,ao.geo.rotation=function(n){function t(t){return t=n(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t}return n=se(n[0]%360*Yo,n[1]*Yo,n.length>2?n[2]*Yo:0),t.invert=function(t){return t=n.invert(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t},t},fe.invert=ce,ao.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=se(-n[0]*Yo,-n[1]*Yo,0).invert,i=[];return e(null,null,1,{point:function(n,e){i.push(n=t(n,e)),n[0]*=Zo,n[1]*=Zo}}),{type:"Polygon",coordinates:[i]}}var t,e,r=[0,0],i=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=ve((t=+r)*Yo,i*Yo),n):t},n.precision=function(r){return arguments.length?(e=ve(t*Yo,(i=+r)*Yo),n):i},n.angle(90)},ao.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Yo,i=n[1]*Yo,u=t[1]*Yo,o=Math.sin(r),a=Math.cos(r),l=Math.sin(i),c=Math.cos(i),f=Math.sin(u),s=Math.cos(u);return Math.atan2(Math.sqrt((e=s*o)*e+(e=c*f-l*s*a)*e),l*f+c*s*a)},ao.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ao.range(Math.ceil(u/d)*d,i,d).map(h).concat(ao.range(Math.ceil(c/y)*y,l,y).map(p)).concat(ao.range(Math.ceil(r/g)*g,e,g).filter(function(n){return xo(n%d)>Uo}).map(f)).concat(ao.range(Math.ceil(a/v)*v,o,v).filter(function(n){return xo(n%y)>Uo}).map(s))}var e,r,i,u,o,a,l,c,f,s,h,p,g=10,v=g,d=90,y=360,m=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(u).concat(p(l).slice(1),h(i).reverse().slice(1),p(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(u=+t[0][0],i=+t[1][0],c=+t[0][1],l=+t[1][1],u>i&&(t=u,u=i,i=t),c>l&&(t=c,c=l,l=t),n.precision(m)):[[u,c],[i,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(m)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],y=+t[1],n):[d,y]},n.minorStep=function(t){return arguments.length?(g=+t[0],v=+t[1],n):[g,v]},n.precision=function(t){return arguments.length?(m=+t,f=ye(a,o,90),s=me(r,e,m),h=ye(c,l,90),p=me(u,i,m),n):m},n.majorExtent([[-180,-90+Uo],[180,90-Uo]]).minorExtent([[-180,-80-Uo],[180,80+Uo]])},ao.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||i.apply(this,arguments)]}}var t,e,r=Me,i=xe;return n.distance=function(){return ao.geo.distance(t||r.apply(this,arguments),e||i.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(i=t,e="function"==typeof t?null:t,n):i},n.precision=function(){return arguments.length?n:0},n},ao.geo.interpolate=function(n,t){return be(n[0]*Yo,n[1]*Yo,t[0]*Yo,t[1]*Yo)},ao.geo.length=function(n){return Ja=0,ao.geo.stream(n,Ga),Ja};var Ja,Ga={sphere:b,point:b,lineStart:_e,lineEnd:b,polygonStart:b,polygonEnd:b},Ka=we(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ao.geo.azimuthalEqualArea=function(){return oe(Ka)}).raw=Ka;var Qa=we(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},m);(ao.geo.azimuthalEquidistant=function(){return oe(Qa)}).raw=Qa,(ao.geo.conicConformal=function(){return Vt(Se)}).raw=Se,(ao.geo.conicEquidistant=function(){return Vt(ke)}).raw=ke;var nl=we(function(n){return 1/n},Math.atan);(ao.geo.gnomonic=function(){return oe(nl)}).raw=nl,Ne.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Io]},(ao.geo.mercator=function(){return Ee(Ne)}).raw=Ne;var tl=we(function(){return 1},Math.asin);(ao.geo.orthographic=function(){return oe(tl)}).raw=tl;var el=we(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ao.geo.stereographic=function(){return oe(el)}).raw=el,Ae.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Io]},(ao.geo.transverseMercator=function(){var n=Ee(Ae),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ae,ao.geom={},ao.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,i=En(e),u=En(r),o=n.length,a=[],l=[];for(t=0;o>t;t++)a.push([+i.call(this,n[t],t),+u.call(this,n[t],t),t]);for(a.sort(qe),t=0;o>t;t++)l.push([a[t][0],-a[t][1]]);var c=Le(a),f=Le(l),s=f[0]===c[0],h=f[f.length-1]===c[c.length-1],p=[];for(t=c.length-1;t>=0;--t)p.push(n[a[c[t]][2]]);for(t=+s;t=r&&c.x<=u&&c.y>=i&&c.y<=o?[[r,o],[u,o],[u,i],[r,i]]:[];f.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(u(n,t)/Uo)*Uo,y:Math.round(o(n,t)/Uo)*Uo,i:t}})}var r=Ce,i=ze,u=r,o=i,a=sl;return n?t(n):(t.links=function(n){return ar(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return ar(e(n)).cells.forEach(function(e,r){for(var i,u,o=e.site,a=e.edges.sort(Ve),l=-1,c=a.length,f=a[c-1].edge,s=f.l===o?f.r:f.l;++l=c,h=r>=f,p=h<<1|s;n.leaf=!1,n=n.nodes[p]||(n.nodes[p]=hr()),s?i=c:a=c,h?o=f:l=f,u(n,t,e,r,i,o,a,l)}var f,s,h,p,g,v,d,y,m,M=En(a),x=En(l);if(null!=t)v=t,d=e,y=r,m=i;else if(y=m=-(v=d=1/0),s=[],h=[],g=n.length,o)for(p=0;g>p;++p)f=n[p],f.xy&&(y=f.x),f.y>m&&(m=f.y),s.push(f.x),h.push(f.y);else for(p=0;g>p;++p){var b=+M(f=n[p],p),_=+x(f,p);v>b&&(v=b),d>_&&(d=_),b>y&&(y=b),_>m&&(m=_),s.push(b),h.push(_)}var w=y-v,S=m-d;w>S?m=d+w:y=v+S;var k=hr();if(k.add=function(n){u(k,n,+M(n,++p),+x(n,p),v,d,y,m)},k.visit=function(n){pr(n,k,v,d,y,m)},k.find=function(n){return gr(k,n[0],n[1],v,d,y,m)},p=-1,null==t){for(;++p=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=vl.get(e)||gl,r=dl.get(r)||m,br(r(e.apply(null,lo.call(arguments,1))))},ao.interpolateHcl=Rr,ao.interpolateHsl=Dr,ao.interpolateLab=Pr,ao.interpolateRound=Ur,ao.transform=function(n){var t=fo.createElementNS(ao.ns.prefix.svg,"g");return(ao.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new jr(e?e.matrix:yl)})(n)},jr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var yl={a:1,b:0,c:0,d:1,e:0,f:0};ao.interpolateTransform=$r,ao.layout={},ao.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++ea*a/y){if(v>l){var c=t.charge/l;n.px-=u*c,n.py-=o*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=u*c,n.py-=o*c}}return!t.charge}}function t(n){n.px=ao.event.x,n.py=ao.event.y,l.resume()}var e,r,i,u,o,a,l={},c=ao.dispatch("start","tick","end"),f=[1,1],s=.9,h=ml,p=Ml,g=-30,v=xl,d=.1,y=.64,M=[],x=[];return l.tick=function(){if((i*=.99)<.005)return e=null,c.end({type:"end",alpha:i=0}),!0;var t,r,l,h,p,v,y,m,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,p=l.target,m=p.x-h.x,b=p.y-h.y,(v=m*m+b*b)&&(v=i*o[r]*((v=Math.sqrt(v))-u[r])/v,m*=v,b*=v,p.x-=m*(y=h.weight+p.weight?h.weight/(h.weight+p.weight):.5),p.y-=b*y,h.x+=m*(y=1-y),h.y+=b*y);if((y=i*d)&&(m=f[0]/2,b=f[1]/2,r=-1,y))for(;++r<_;)l=M[r],l.x+=(m-l.x)*y,l.y+=(b-l.y)*y;if(g)for(ri(t=ao.geom.quadtree(M),i,a),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*s,l.y-=(l.py-(l.py=l.y))*s);c.tick({type:"tick",alpha:i})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(f=n,l):f},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.friction=function(n){return arguments.length?(s=+n,l):s},l.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(y=n*n,l):Math.sqrt(y)},l.alpha=function(n){return arguments.length?(n=+n,i?n>0?i=n:(e.c=null,e.t=NaN,e=null,c.end({type:"end",alpha:i=0})):n>0&&(c.start({type:"start",alpha:i=n}),e=qn(l.tick)),l):i},l.start=function(){function n(n,r){if(!e){for(e=new Array(i),l=0;i>l;++l)e[l]=[];for(l=0;c>l;++l){var u=x[l];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var o,a=e[t],l=-1,f=a.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;i>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",s)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof h)for(t=0;c>t;++t)u[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)u[t]=h;if(o=[],"function"==typeof p)for(t=0;c>t;++t)o[t]=+p.call(this,x[t],t);else for(t=0;c>t;++t)o[t]=p;if(a=[],"function"==typeof g)for(t=0;i>t;++t)a[t]=+g.call(this,M[t],t);else for(t=0;i>t;++t)a[t]=g;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=ao.behavior.drag().origin(m).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",ni)),arguments.length?void this.on("mouseover.force",ti).on("mouseout.force",ei).call(r):r},ao.rebind(l,c,"on")};var ml=20,Ml=1,xl=1/0;ao.layout.hierarchy=function(){function n(i){var u,o=[i],a=[];for(i.depth=0;null!=(u=o.pop());)if(a.push(u),(c=e.call(n,u,u.depth))&&(l=c.length)){for(var l,c,f;--l>=0;)o.push(f=c[l]),f.parent=u,f.depth=u.depth+1;r&&(u.value=0),u.children=c}else r&&(u.value=+r.call(n,u,u.depth)||0),delete u.children;return oi(i,function(n){var e,i;t&&(e=n.children)&&e.sort(t),r&&(i=n.parent)&&(i.value+=n.value)}),a}var t=ci,e=ai,r=li;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(ui(t,function(n){n.children&&(n.value=0)}),oi(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},ao.layout.partition=function(){function n(t,e,r,i){var u=t.children;if(t.x=e,t.y=t.depth*i,t.dx=r,t.dy=i,u&&(o=u.length)){var o,a,l,c=-1;for(r=t.value?r/t.value:0;++cs?-1:1),g=ao.sum(c),v=g?(s-l*p)/g:0,d=ao.range(l),y=[];return null!=e&&d.sort(e===bl?function(n,t){return c[t]-c[n]}:function(n,t){return e(o[n],o[t])}),d.forEach(function(n){y[n]={data:o[n],value:a=c[n],startAngle:f,endAngle:f+=a*v+p,padAngle:h}}),y}var t=Number,e=bl,r=0,i=Ho,u=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(i=t,n):i},n.padAngle=function(t){return arguments.length?(u=t,n):u},n};var bl={};ao.layout.stack=function(){function n(a,l){if(!(h=a.length))return a;var c=a.map(function(e,r){return t.call(n,e,r)}),f=c.map(function(t){return t.map(function(t,e){return[u.call(n,t,e),o.call(n,t,e)]})}),s=e.call(n,f,l);c=ao.permute(c,s),f=ao.permute(f,s);var h,p,g,v,d=r.call(n,f,l),y=c[0].length;for(g=0;y>g;++g)for(i.call(n,c[0][g],v=d[g],f[0][g][1]),p=1;h>p;++p)i.call(n,c[p][g],v+=f[p-1][g][1],f[p][g][1]);return a}var t=m,e=gi,r=vi,i=pi,u=si,o=hi;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:_l.get(t)||gi,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:wl.get(t)||vi,n):r},n.x=function(t){return arguments.length?(u=t,n):u},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(i=t,n):i},n};var _l=ao.map({"inside-out":function(n){var t,e,r=n.length,i=n.map(di),u=n.map(yi),o=ao.range(r).sort(function(n,t){return i[n]-i[t]}),a=0,l=0,c=[],f=[];for(t=0;r>t;++t)e=o[t],l>a?(a+=u[e],c.push(e)):(l+=u[e],f.push(e));return f.reverse().concat(c)},reverse:function(n){return ao.range(n.length).reverse()},"default":gi}),wl=ao.map({silhouette:function(n){var t,e,r,i=n.length,u=n[0].length,o=[],a=0,l=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;u>e;++e)l[e]=(a-o[e])/2;return l},wiggle:function(n){var t,e,r,i,u,o,a,l,c,f=n.length,s=n[0],h=s.length,p=[];for(p[0]=l=c=0,e=1;h>e;++e){for(t=0,i=0;f>t;++t)i+=n[t][e][1];for(t=0,u=0,a=s[e][0]-s[e-1][0];f>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;u+=o*n[t][e][1]}p[e]=l-=i?u/i*a:0,c>l&&(c=l)}for(e=0;h>e;++e)p[e]-=c;return p},expand:function(n){var t,e,r,i=n.length,u=n[0].length,o=1/i,a=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];if(r)for(t=0;i>t;t++)n[t][e][1]/=r;else for(t=0;i>t;t++)n[t][e][1]=o}for(e=0;u>e;++e)a[e]=0;return a},zero:vi});ao.layout.histogram=function(){function n(n,u){for(var o,a,l=[],c=n.map(e,this),f=r.call(this,c,u),s=i.call(this,f,c,u),u=-1,h=c.length,p=s.length-1,g=t?1:1/h;++u0)for(u=-1;++u=f[0]&&a<=f[1]&&(o=l[ao.bisect(s,a,1,p)-1],o.y+=g,o.push(n[u]));return l}var t=!0,e=Number,r=bi,i=Mi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(i="number"==typeof t?function(n){return xi(n,t)}:En(t),n):i},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ao.layout.pack=function(){function n(n,u){var o=e.call(this,n,u),a=o[0],l=i[0],c=i[1],f=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,oi(a,function(n){n.r=+f(n.value)}),oi(a,Ni),r){var s=r*(t?1:Math.max(2*a.r/l,2*a.r/c))/2;oi(a,function(n){n.r+=s}),oi(a,Ni),oi(a,function(n){n.r-=s})}return Ci(a,l/2,c/2,t?1:1/Math.max(2*a.r/l,2*a.r/c)),o}var t,e=ao.layout.hierarchy().sort(_i),r=0,i=[1,1];return n.size=function(t){return arguments.length?(i=t,n):i},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},ii(n,e)},ao.layout.tree=function(){function n(n,i){var f=o.call(this,n,i),s=f[0],h=t(s);if(oi(h,e),h.parent.m=-h.z,ui(h,r),c)ui(s,u);else{var p=s,g=s,v=s;ui(s,function(n){n.xg.x&&(g=n),n.depth>v.depth&&(v=n)});var d=a(p,g)/2-p.x,y=l[0]/(g.x+a(g,p)/2+d),m=l[1]/(v.depth||1);ui(s,function(n){n.x=(n.x+d)*y,n.y=n.depth*m})}return f}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var i,u=t.children,o=0,a=u.length;a>o;++o)r.push((u[o]=i={_:u[o],parent:t,children:(i=u[o].children)&&i.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=i);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Di(n);var u=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-u):n.z=u}else r&&(n.z=r.z+a(n._,r._));n.parent.A=i(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function i(n,t,e){if(t){for(var r,i=n,u=n,o=t,l=i.parent.children[0],c=i.m,f=u.m,s=o.m,h=l.m;o=Ti(o),i=qi(i),o&&i;)l=qi(l),u=Ti(u),u.a=n,r=o.z+s-i.z-c+a(o._,i._),r>0&&(Ri(Pi(o,n,e),n,r),c+=r,f+=r),s+=o.m,c+=i.m,h+=l.m,f+=u.m;o&&!Ti(u)&&(u.t=o,u.m+=s-f),i&&!qi(l)&&(l.t=i,l.m+=c-h,e=n)}return e}function u(n){n.x*=l[0],n.y=n.depth*l[1]}var o=ao.layout.hierarchy().sort(null).value(null),a=Li,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(c=null==(l=t)?u:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:u,n):c?l:null},ii(n,o)},ao.layout.cluster=function(){function n(n,u){var o,a=t.call(this,n,u),l=a[0],c=0;oi(l,function(n){var t=n.children;t&&t.length?(n.x=ji(t),n.y=Ui(t)):(n.x=o?c+=e(n,o):0,n.y=0,o=n)});var f=Fi(l),s=Hi(l),h=f.x-e(f,s)/2,p=s.x+e(s,f)/2;return oi(l,i?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(p-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),a}var t=ao.layout.hierarchy().sort(null).value(null),e=Li,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ao.layout.treemap=function(){function n(n,t){for(var e,r,i=-1,u=n.length;++it?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var u=e.children;if(u&&u.length){var o,a,l,c=s(e),f=[],h=u.slice(),g=1/0,v="slice"===p?c.dx:"dice"===p?c.dy:"slice-dice"===p?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),f.area=0;(l=h.length)>0;)f.push(o=h[l-1]),f.area+=o.area,"squarify"!==p||(a=r(f,v))<=g?(h.pop(),g=a):(f.area-=f.pop().area,i(f,v,c,!1),v=Math.min(c.dx,c.dy),f.length=f.area=0,g=1/0);f.length&&(i(f,v,c,!0),f.length=f.area=0),u.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var u,o=s(t),a=r.slice(),l=[];for(n(a,o.dx*o.dy/t.value),l.area=0;u=a.pop();)l.push(u),l.area+=u.area,null!=u.z&&(i(l,u.z?o.dx:o.dy,o,!a.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,i=0,u=1/0,o=-1,a=n.length;++oe&&(u=e),e>i&&(i=e));return r*=r,t*=t,r?Math.max(t*i*g/r,r/(t*u*g)):1/0}function i(n,t,e,r){var i,u=-1,o=n.length,a=e.x,c=e.y,f=t?l(n.area/t):0; -if(t==e.dx){for((r||f>e.dy)&&(f=e.dy);++ue.dx)&&(f=e.dx);++ue&&(t=1),1>e&&(n=0),function(){var e,r,i;do e=2*Math.random()-1,r=2*Math.random()-1,i=e*e+r*r;while(!i||i>1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var n=ao.random.normal.apply(ao,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ao.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ao.scale={};var Sl={floor:m,ceil:m};ao.scale.linear=function(){return Wi([0,1],[0,1],Mr,!1)};var kl={s:1,g:1,p:1,r:1,e:1};ao.scale.log=function(){return ru(ao.scale.linear().domain([0,1]),10,!0,[1,10])};var Nl=ao.format(".0e"),El={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ao.scale.pow=function(){return iu(ao.scale.linear(),1,[0,1])},ao.scale.sqrt=function(){return ao.scale.pow().exponent(.5)},ao.scale.ordinal=function(){return ou([],{t:"range",a:[[]]})},ao.scale.category10=function(){return ao.scale.ordinal().range(Al)},ao.scale.category20=function(){return ao.scale.ordinal().range(Cl)},ao.scale.category20b=function(){return ao.scale.ordinal().range(zl)},ao.scale.category20c=function(){return ao.scale.ordinal().range(Ll)};var Al=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Cl=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),zl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),Ll=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);ao.scale.quantile=function(){return au([],[])},ao.scale.quantize=function(){return lu(0,1,[0,1])},ao.scale.threshold=function(){return cu([.5],[0,1])},ao.scale.identity=function(){return fu([0,1])},ao.svg={},ao.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),f=o.apply(this,arguments)-Io,s=a.apply(this,arguments)-Io,h=Math.abs(s-f),p=f>s?0:1;if(n>c&&(g=c,c=n,n=g),h>=Oo)return t(c,p)+(n?t(n,1-p):"")+"Z";var g,v,d,y,m,M,x,b,_,w,S,k,N=0,E=0,A=[];if((y=(+l.apply(this,arguments)||0)/2)&&(d=u===ql?Math.sqrt(n*n+c*c):+u.apply(this,arguments),p||(E*=-1),c&&(E=tn(d/c*Math.sin(y))),n&&(N=tn(d/n*Math.sin(y)))),c){m=c*Math.cos(f+E),M=c*Math.sin(f+E),x=c*Math.cos(s-E),b=c*Math.sin(s-E);var C=Math.abs(s-f-2*E)<=Fo?0:1;if(E&&yu(m,M,x,b)===p^C){var z=(f+s)/2;m=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else m=M=0;if(n){_=n*Math.cos(s-N),w=n*Math.sin(s-N),S=n*Math.cos(f+N),k=n*Math.sin(f+N);var L=Math.abs(f-s+2*N)<=Fo?0:1;if(N&&yu(_,w,S,k)===1-p^L){var q=(f+s)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Uo&&(g=Math.min(Math.abs(c-n)/2,+i.apply(this,arguments)))>.001){v=c>n^p?0:1;var T=g,R=g;if(Fo>h){var D=null==S?[_,w]:null==x?[m,M]:Re([m,M],[S,k],[x,b],[_,w]),P=m-D[0],U=M-D[1],j=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*j+U*F)/(Math.sqrt(P*P+U*U)*Math.sqrt(j*j+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(g,(n-O)/(H-1)),T=Math.min(g,(c-O)/(H+1))}if(null!=x){var I=mu(null==S?[_,w]:[S,k],[m,M],c,T,p),Y=mu([x,b],[_,w],c,T,p);g===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-p^yu(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",p," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",m,",",M);if(null!=S){var Z=mu([m,M],[S,k],n,-R,p),V=mu([_,w],null==x?[m,M]:[x,b],n,-R,p);g===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",p^yu(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-p," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",m,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",p," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-p," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hu,r=pu,i=su,u=ql,o=gu,a=vu,l=du;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(i=En(t),n):i},n.padRadius=function(t){return arguments.length?(u=t==ql?ql:En(t),n):u},n.startAngle=function(t){return arguments.length?(o=En(t),n):o},n.endAngle=function(t){return arguments.length?(a=En(t),n):a},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Io;return[Math.cos(t)*n,Math.sin(t)*n]},n};var ql="auto";ao.svg.line=function(){return Mu(m)};var Tl=ao.map({linear:xu,"linear-closed":bu,step:_u,"step-before":wu,"step-after":Su,basis:zu,"basis-open":Lu,"basis-closed":qu,bundle:Tu,cardinal:Eu,"cardinal-open":ku,"cardinal-closed":Nu,monotone:Fu});Tl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Rl=[0,2/3,1/3,0],Dl=[0,1/3,2/3,0],Pl=[0,1/6,2/3,1/6];ao.svg.line.radial=function(){var n=Mu(Hu);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wu.reverse=Su,Su.reverse=wu,ao.svg.area=function(){return Ou(m)},ao.svg.area.radial=function(){var n=Ou(Hu);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ao.svg.chord=function(){function n(n,a){var l=t(this,u,n,a),c=t(this,o,n,a);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?i(l.r,l.p1,l.r,l.p0):i(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+i(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var i=t.call(n,e,r),u=a.call(n,i,r),o=l.call(n,i,r)-Io,f=c.call(n,i,r)-Io;return{r:u,a0:o,a1:f,p0:[u*Math.cos(o),u*Math.sin(o)],p1:[u*Math.cos(f),u*Math.sin(f)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Fo)+",1 "+t}function i(n,t,e,r){return"Q 0,0 "+r}var u=Me,o=xe,a=Iu,l=gu,c=vu;return n.radius=function(t){return arguments.length?(a=En(t),n):a},n.source=function(t){return arguments.length?(u=En(t),n):u},n.target=function(t){return arguments.length?(o=En(t),n):o},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},ao.svg.diagonal=function(){function n(n,i){var u=t.call(this,n,i),o=e.call(this,n,i),a=(u.y+o.y)/2,l=[u,{x:u.x,y:a},{x:o.x,y:a},o];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yu;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ao.svg.diagonal.radial=function(){var n=ao.svg.diagonal(),t=Yu,e=n.projection;return n.projection=function(n){return arguments.length?e(Zu(t=n)):t},n},ao.svg.symbol=function(){function n(n,r){return(Ul.get(t.call(this,n,r))||$u)(e.call(this,n,r))}var t=Xu,e=Vu;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Ul=ao.map({circle:$u,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Fl)),e=t*Fl;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ao.svg.symbolTypes=Ul.keys();var jl=Math.sqrt(3),Fl=Math.tan(30*Yo);Co.transition=function(n){for(var t,e,r=Hl||++Zl,i=Ku(n),u=[],o=Ol||{time:Date.now(),ease:Nr,delay:0,duration:250},a=-1,l=this.length;++au;u++){i.push(t=[]);for(var e=this[u],a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return Wu(i,this.namespace,this.id)},Yl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(i){i[r][e].tween.set(n,t)})},Yl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function i(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function u(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?$r:Mr,a=ao.ns.qualify(n);return Ju(this,"attr."+n,t,a.local?u:i)},Yl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(i));return r&&function(n){this.setAttribute(i,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(i.space,i.local));return r&&function(n){this.setAttributeNS(i.space,i.local,r(n))}}var i=ao.ns.qualify(n);return this.tween("attr."+n,i.local?r:e)},Yl.style=function(n,e,r){function i(){this.style.removeProperty(n)}function u(e){return null==e?i:(e+="",function(){var i,u=t(this).getComputedStyle(this,null).getPropertyValue(n);return u!==e&&(i=Mr(u,e),function(t){this.style.setProperty(n,i(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ju(this,"style."+n,e,u)},Yl.styleTween=function(n,e,r){function i(i,u){var o=e.call(this,i,u,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,i)},Yl.text=function(n){return Ju(this,"text",n,Gu)},Yl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Yl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ao.ease.apply(ao,arguments)),Y(this,function(r){r[e][t].ease=n}))},Yl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,i,u){r[e][t].delay=+n.call(r,r.__data__,i,u)}:(n=+n,function(r){r[e][t].delay=n}))},Yl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,i,u){r[e][t].duration=Math.max(1,n.call(r,r.__data__,i,u))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Yl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var i=Ol,u=Hl;try{Hl=e,Y(this,function(t,i,u){Ol=t[r][e],n.call(t,t.__data__,i,u)})}finally{Ol=i,Hl=u}}else Y(this,function(i){var u=i[r][e];(u.event||(u.event=ao.dispatch("start","end","interrupt"))).on(n,t)});return this},Yl.transition=function(){for(var n,t,e,r,i=this.id,u=++Zl,o=this.namespace,a=[],l=0,c=this.length;c>l;l++){a.push(n=[]);for(var t=this[l],f=0,s=t.length;s>f;f++)(e=t[f])&&(r=e[o][i],Qu(e,f,o,u,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wu(a,o,u)},ao.svg.axis=function(){function n(n){n.each(function(){var n,c=ao.select(this),f=this.__chart__||e,s=this.__chart__=e.copy(),h=null==l?s.ticks?s.ticks.apply(s,a):s.domain():l,p=null==t?s.tickFormat?s.tickFormat.apply(s,a):m:t,g=c.selectAll(".tick").data(h,s),v=g.enter().insert("g",".domain").attr("class","tick").style("opacity",Uo),d=ao.transition(g.exit()).style("opacity",Uo).remove(),y=ao.transition(g.order()).style("opacity",1),M=Math.max(i,0)+o,x=Zi(s),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),ao.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=y.select("line"),C=g.select("text").text(p),z=v.select("text"),L=y.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=no,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*u+"V0H"+x[1]+"V"+q*u)):(n=to,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*u+","+x[0]+"H0V"+x[1]+"H"+q*u)),E.attr(N,q*i),z.attr(k,q*M),A.attr(S,0).attr(N,q*i),L.attr(w,0).attr(k,q*M),s.rangeBand){var T=s,R=T.rangeBand()/2;f=s=function(n){return T(n)+R}}else f.rangeBand?f=s:d.call(n,s,f);v.call(n,f,s),y.call(n,s,s)})}var t,e=ao.scale.linear(),r=Vl,i=6,u=6,o=3,a=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Xl?t+"":Vl,n):r},n.ticks=function(){return arguments.length?(a=co(arguments),n):a},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(i=+t,u=+arguments[e-1],n):i},n.innerTickSize=function(t){return arguments.length?(i=+t,n):i},n.outerTickSize=function(t){return arguments.length?(u=+t,n):u},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Vl="bottom",Xl={top:1,right:1,bottom:1,left:1};ao.svg.brush=function(){function n(t){t.each(function(){var t=ao.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,m);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return $l[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,s=ao.transition(t),h=ao.transition(o);c&&(l=Zi(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(s)),f&&(l=Zi(f),h.attr("y",l[0]).attr("height",l[1]-l[0]),i(s)),e(s)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",s[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1]-s[0])}function i(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function u(){function u(){32==ao.event.keyCode&&(C||(M=null,L[0]-=s[1],L[1]-=h[1],C=2),S())}function v(){32==ao.event.keyCode&&2==C&&(L[0]+=s[1],L[1]+=h[1],C=0,S())}function d(){var n=ao.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(ao.event.altKey?(M||(M=[(s[0]+s[1])/2,(h[0]+h[1])/2]),L[0]=s[+(n[0]f?(i=r,r=f):i=f),v[0]!=r||v[1]!=i?(e?a=null:o=null,v[0]=r,v[1]=i,!0):void 0}function m(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ao.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=ao.select(ao.event.target),w=l.of(b,arguments),k=ao.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&f,C=_.classed("extent"),z=W(b),L=ao.mouse(b),q=ao.select(t(b)).on("keydown.brush",u).on("keyup.brush",v);if(ao.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",m):q.on("mousemove.brush",d).on("mouseup.brush",m),k.interrupt().selectAll("*").interrupt(),C)L[0]=s[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[s[1-T]-L[0],h[1-R]-L[1]],L[0]=s[T],L[1]=h[R]}else ao.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),ao.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,l=N(n,"brushstart","brush","brushend"),c=null,f=null,s=[0,0],h=[0,0],p=!0,g=!0,v=Bl[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:s,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Hl?ao.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,s=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(s,t.x),r=xr(h,t.y);return o=a=null,function(i){s=t.x=e(i),h=t.y=r(i),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=Bl[!c<<1|!f],n):c},n.y=function(t){return arguments.length?(f=t,v=Bl[!c<<1|!f],n):f},n.clamp=function(t){return arguments.length?(c&&f?(p=!!t[0],g=!!t[1]):c?p=!!t:f&&(g=!!t),n):c&&f?[p,g]:c?p:f?g:null},n.extent=function(t){var e,r,i,u,l;return arguments.length?(c&&(e=t[0],r=t[1],f&&(e=e[0],r=r[0]),o=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),e==s[0]&&r==s[1]||(s=[e,r])),f&&(i=t[0],u=t[1],c&&(i=i[1],u=u[1]),a=[i,u],f.invert&&(i=f(i),u=f(u)),i>u&&(l=i,i=u,u=l),i==h[0]&&u==h[1]||(h=[i,u])),n):(c&&(o?(e=o[0],r=o[1]):(e=s[0],r=s[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),f&&(a?(i=a[0],u=a[1]):(i=h[0],u=h[1],f.invert&&(i=f.invert(i),u=f.invert(u)),i>u&&(l=i,i=u,u=l))),c&&f?[[e,i],[r,u]]:c?[e,r]:f&&[i,u])},n.clear=function(){return n.empty()||(s=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!c&&s[0]==s[1]||!!f&&h[0]==h[1]},ao.rebind(n,l,"on")};var $l={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Bl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Wl=ga.format=xa.timeFormat,Jl=Wl.utc,Gl=Jl("%Y-%m-%dT%H:%M:%S.%LZ");Wl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?eo:Gl,eo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},eo.toString=Gl.toString,ga.second=On(function(n){return new va(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ga.seconds=ga.second.range,ga.seconds.utc=ga.second.utc.range,ga.minute=On(function(n){return new va(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ga.minutes=ga.minute.range,ga.minutes.utc=ga.minute.utc.range,ga.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new va(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ga.hours=ga.hour.range,ga.hours.utc=ga.hour.utc.range,ga.month=On(function(n){return n=ga.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ga.months=ga.month.range,ga.months.utc=ga.month.utc.range;var Kl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ql=[[ga.second,1],[ga.second,5],[ga.second,15],[ga.second,30],[ga.minute,1],[ga.minute,5],[ga.minute,15],[ga.minute,30],[ga.hour,1],[ga.hour,3],[ga.hour,6],[ga.hour,12],[ga.day,1],[ga.day,2],[ga.week,1],[ga.month,1],[ga.month,3],[ga.year,1]],nc=Wl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),tc={range:function(n,t,e){return ao.range(Math.ceil(n/e)*e,+t,e).map(io)},floor:m,ceil:m};Ql.year=ga.year,ga.scale=function(){return ro(ao.scale.linear(),Ql,nc)};var ec=Ql.map(function(n){return[n[0].utc,n[1]]}),rc=Jl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);ec.year=ga.year.utc,ga.scale.utc=function(){return ro(ao.scale.linear(),ec,rc)},ao.text=An(function(n){return n.responseText}),ao.json=function(n,t){return Cn(n,"application/json",uo,t)},ao.html=function(n,t){return Cn(n,"text/html",oo,t)},ao.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=ao,define(ao)):"object"==typeof module&&module.exports?module.exports=ao:this.d3=ao}(); \ No newline at end of file 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/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)