diff --git a/R/makeChartConfig.R b/R/makeChartConfig.R
index 19a380e7..5426ce4e 100644
--- a/R/makeChartConfig.R
+++ b/R/makeChartConfig.R
@@ -56,12 +56,17 @@ makeChartConfig <- function(dirs, sourceFiles=TRUE){
chart <- read_yaml(path)
chart$path <- path
chart$name <- path %>% file_path_sans_ext %>% basename
+ chart$order <- ifelse(
+ is.null(chart$order),
+ length(yaml_files) + 1,
+ chart$order
+ ) %>% as.numeric
+
return(chart)
})
-
-
names(charts) <- yaml_files %>% file_path_sans_ext %>% basename
+ charts <- charts[order(purrr::map_dbl(charts, function(chart) chart$order))]
message("Found ", length(yaml_files), " config files: ",paste(names(charts),collapse=", "))
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 (
" +
- 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):