(function() {
"use strict";
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(['d3', 'jquery'], factory);
} else if (typeof exports === 'object') {
module.exports = factory(require('d3'), require('jquery'));
} else {
root.MG = factory(root.d3, root.jQuery);
}
}(this, function(d3, $) {
window.MG = {version: '2.2.1'};
var charts = {};
MG.globals = {};
MG.deprecations = {
rollover_callback: { replacement: 'mouseover', version: '2.0' },
rollout_callback: { replacement: 'mouseout', version: '2.0' },
show_years: { replacement: 'show_secondary_x_label', version: '2.1' }
};
MG.globals.link = false;
MG.globals.version = "1.1";
MG.data_graphic = function() {
'use strict';
var defaults = {};
defaults.all = {
missing_is_zero: false, // if true, missing values will be treated as zeros
missing_is_hidden: false, // if true, missing values will appear as broken segments
legend: '' , // an array identifying the labels for a chart's lines
legend_target: '', // if set, the specified element is populated with a legend
error: '', // if set, a graph will show an error icon and log the error to the console
animate_on_load: false, // animate lines on load
top: 40, // the size of the top margin
bottom: 30, // the size of the bottom margin
right: 10, // size of the right margin
left: 50, // size of the left margin
buffer: 8, // the buffer between the actual chart area and the margins
width: 350, // the width of the entire graphic
height: 220, // the height of the entire graphic
full_width: false, // sets the graphic width to be the width of the parent element and resizes dynamically
full_height: false, // sets the graphic width to be the width of the parent element and resizes dynamically
small_height_threshold: 120, // the height threshold for when smaller text appears
small_width_threshold: 160, // the width threshold for when smaller text appears
small_text: false, // coerces small text regardless of graphic size
xax_count: 6, // number of x axis ticks
xax_tick_length: 5, // x axis tick length
yax_count: 5, // number of y axis ticks
yax_tick_length: 5, // y axis tick length
x_extended_ticks: false, // extends x axis ticks across chart - useful for tall charts
y_extended_ticks: false, // extends y axis ticks across chart - useful for long charts
y_scale_type: 'linear',
max_x: null,
max_y: null,
min_x: null,
min_y: null, // if set, y axis starts at an arbitrary value
min_y_from_data: false, // if set, y axis will start at minimum value rather than at 0
point_size: 2.5, // the size of the dot that appears on a line on mouse-over
x_accessor: 'date',
xax_units: '',
x_label: '',
x_axis: true,
y_axis: true,
y_accessor: 'value',
y_label: '',
yax_units: '',
x_rug: false,
y_rug: false,
transition_on_update: true,
mouseover: null,
show_rollover_text: true,
show_confidence_band: null, // given [l, u] shows a confidence at each point from l to u
xax_format: null, // xax_format is a function that formats the labels for the x axis.
area: true,
chart_type: 'line',
data: [],
decimals: 2, // the number of decimals in any rollover
format: 'count', // format = {count, percentage}
inflator: 10/9, // for setting y axis max
linked: false, // links together all other graphs with linked:true, so rollovers in one trigger rollovers in the others
linked_format: '%Y-%m-%d', // What granularity to link on for graphs. Default is at day
list: false,
baselines: null, // sets the baseline lines
markers: null, // sets the marker lines
scalefns: {},
scales: {},
show_secondary_x_label: true,
target: '#viz',
interpolate: 'cardinal', // interpolation method to use when rendering lines
custom_line_color_map: [], // allows arbitrary mapping of lines to colors, e.g. [2,3] will map line 1 to color 2 and line 2 to color 3
max_data_size: null, // explicitly specify the the max number of line series, for use with custom_line_color_map
aggregate_rollover: false, // links the lines in a multi-line chart
show_tooltips: true // if enabled, a chart's description will appear in a tooltip (requires jquery)
};
defaults.point = {
buffer: 16,
ls: false,
lowess: false,
point_size: 2.5,
size_accessor: null,
color_accessor: null,
size_range: null, // when we set a size_accessor option, this array determines the size range, e.g. [1,5]
color_range: null, // e.g. ['blue', 'red'] to color different groups of points
size_domain: null,
color_domain: null,
color_type: 'number' // can be either 'number' - the color scale is quantitative - or 'category' - the color scale is qualitative.
};
defaults.histogram = {
mouseover: function(d, i) {
d3.select('#histogram svg .mg-active-datapoint')
.text('Frequency Count: ' + d.y);
},
binned: false,
bins: null,
processed_x_accessor: 'x',
processed_y_accessor: 'y',
processed_dx_accessor: 'dx',
bar_margin: 1
};
defaults.bar = {
y_accessor: 'factor',
x_accessor: 'value',
baseline_accessor: null,
predictor_accessor: null,
predictor_proportion: 5,
dodge_accessor: null,
binned: true,
padding_percentage: 0,
outer_padding_percentage: 0.1,
height: 500,
top: 20,
bar_height: 20,
left: 70
};
defaults.missing = {
top: 40, // the size of the top margin
bottom: 30, // the size of the bottom margin
right: 10, // size of the right margin
left: 10, // size of the left margin
buffer: 8, // the buffer between the actual chart area and the margins
legend_target: '',
width: 350,
height: 220,
missing_text: 'Data currently missing or unavailable',
scalefns: {},
scales: {},
show_missing_background: true,
interpolate: 'cardinal'
};
var args = arguments[0];
if (!args) { args = {}; }
if (args.list) {
args.x_accessor = 0;
args.y_accessor = 1;
}
// check for deprecated parameters
for (var key in MG.deprecations) {
if (args.hasOwnProperty(key)) {
var deprecation = MG.deprecations[key],
message = 'Use of `args.' + key + '` has been deprecated',
replacement = deprecation.replacement,
version;
// transparently alias the deprecated
if (replacement) {
if (args[replacement]) {
message += '. The replacement - `args.' + replacement + '` - has already been defined. This definition will be discarded.';
} else {
args[replacement] = args[key];
}
}
if (deprecation.warned) {
continue;
}
deprecation.warned = true;
if (replacement) {
message += ' in favor of `args.' + replacement + '`';
}
warnDeprecation(message, deprecation.version);
}
}
//build the chart
var a;
if (args.chart_type === 'missing-data') {
args = merge_with_defaults(args, defaults.missing);
charts.missing(args);
}
else if (args.chart_type === 'point') {
a = merge_with_defaults(defaults.point, defaults.all);
args = merge_with_defaults(args, a);
charts.point(args).mainPlot().markers().rollover().windowListeners();
}
else if (args.chart_type === 'histogram') {
a = merge_with_defaults(defaults.histogram, defaults.all);
args = merge_with_defaults(args, a);
charts.histogram(args).mainPlot().markers().rollover().windowListeners();
}
else if (args.chart_type === 'bar') {
a = merge_with_defaults(defaults.bar, defaults.all);
args = merge_with_defaults(args, a);
charts.bar(args).mainPlot().markers().rollover().windowListeners();
}
else {
args = merge_with_defaults(args, defaults.all);
charts.line(args).markers().mainPlot().rollover().windowListeners();
}
return args.data;
};
if (typeof jQuery !== 'undefined') {
/*!
* Bootstrap v3.3.1 (http://getbootstrap.com)
* Copyright 2011-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*!
* Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=698666b23215c58f23d4)
* Config saved to config.json and https://gist.github.com/698666b23215c58f23d4
*/
/* ========================================================================
* Bootstrap: tooltip.js v3.3.1
* http://getbootstrap.com/javascript/#tooltip
* Inspired by the original jQuery.tipsy by Jason Frame
* ========================================================================
* Copyright 2011-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
if(typeof $().tooltip == 'function')
return true;
// TOOLTIP PUBLIC CLASS DEFINITION
// ===============================
var Tooltip = function (element, options) {
this.type =
this.options =
this.enabled =
this.timeout =
this.hoverState =
this.$element = null;
this.init('tooltip', element, options);
};
Tooltip.VERSION = '3.3.1';
Tooltip.TRANSITION_DURATION = 150;
Tooltip.DEFAULTS = {
animation: true,
placement: 'top',
selector: false,
template: '
',
trigger: 'hover focus',
title: '',
delay: 0,
html: false,
container: false,
viewport: {
selector: 'body',
padding: 0
}
};
Tooltip.prototype.init = function (type, element, options) {
this.enabled = true;
this.type = type;
this.$element = $(element);
this.options = this.getOptions(options);
this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport);
var triggers = this.options.trigger.split(' ');
for (var i = triggers.length; i--;) {
var trigger = triggers[i];
if (trigger == 'click') {
this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this));
} else if (trigger != 'manual') {
var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin';
var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout';
this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this));
this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this));
}
}
this.options.selector ?
(this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
this.fixTitle();
};
Tooltip.prototype.getDefaults = function () {
return Tooltip.DEFAULTS;
};
Tooltip.prototype.getOptions = function (options) {
options = $.extend({}, this.getDefaults(), this.$element.data(), options);
if (options.delay && typeof options.delay == 'number') {
options.delay = {
show: options.delay,
hide: options.delay
};
}
return options;
};
Tooltip.prototype.getDelegateOptions = function () {
var options = {};
var defaults = this.getDefaults();
this._options && $.each(this._options, function (key, value) {
if (defaults[key] != value) options[key] = value;
});
return options;
};
Tooltip.prototype.enter = function (obj) {
var self = obj instanceof this.constructor ?
obj : $(obj.currentTarget).data('bs.' + this.type);
if (self && self.$tip && self.$tip.is(':visible')) {
self.hoverState = 'in';
return;
}
if (!self) {
self = new this.constructor(obj.currentTarget, this.getDelegateOptions());
$(obj.currentTarget).data('bs.' + this.type, self);
}
clearTimeout(self.timeout);
self.hoverState = 'in';
if (!self.options.delay || !self.options.delay.show) return self.show();
self.timeout = setTimeout(function () {
if (self.hoverState == 'in') self.show();
}, self.options.delay.show);
};
Tooltip.prototype.leave = function (obj) {
var self = obj instanceof this.constructor ?
obj : $(obj.currentTarget).data('bs.' + this.type);
if (!self) {
self = new this.constructor(obj.currentTarget, this.getDelegateOptions());
$(obj.currentTarget).data('bs.' + this.type, self);
}
clearTimeout(self.timeout);
self.hoverState = 'out';
if (!self.options.delay || !self.options.delay.hide) return self.hide();
self.timeout = setTimeout(function () {
if (self.hoverState == 'out') self.hide();
}, self.options.delay.hide);
};
Tooltip.prototype.show = function () {
var e = $.Event('show.bs.' + this.type);
if (this.hasContent() && this.enabled) {
this.$element.trigger(e);
var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]);
if (e.isDefaultPrevented() || !inDom) return;
var that = this;
var $tip = this.tip();
var tipId = this.getUID(this.type);
this.setContent();
$tip.attr('id', tipId);
this.$element.attr('aria-describedby', tipId);
if (this.options.animation) $tip.addClass('fade');
var placement = typeof this.options.placement == 'function' ?
this.options.placement.call(this, $tip[0], this.$element[0]) :
this.options.placement;
var autoToken = /\s?auto?\s?/i;
var autoPlace = autoToken.test(placement);
if (autoPlace) placement = placement.replace(autoToken, '') || 'top';
$tip
.detach()
.css({ top: 0, left: 0, display: 'block' })
.addClass(placement)
.data('bs.' + this.type, this);
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element);
var pos = this.getPosition();
var actualWidth = $tip[0].offsetWidth;
var actualHeight = $tip[0].offsetHeight;
if (autoPlace) {
var orgPlacement = placement;
var $container = this.options.container ? $(this.options.container) : this.$element.parent();
var containerDim = this.getPosition($container);
placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top' :
placement == 'top' && pos.top - actualHeight < containerDim.top ? 'bottom' :
placement == 'right' && pos.right + actualWidth > containerDim.width ? 'left' :
placement == 'left' && pos.left - actualWidth < containerDim.left ? 'right' :
placement;
$tip
.removeClass(orgPlacement)
.addClass(placement);
}
var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight);
this.applyPlacement(calculatedOffset, placement);
var complete = function () {
var prevHoverState = that.hoverState;
that.$element.trigger('shown.bs.' + that.type);
that.hoverState = null;
if (prevHoverState == 'out') that.leave(that);
};
$.support.transition && this.$tip.hasClass('fade') ?
$tip
.one('bsTransitionEnd', complete)
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
complete();
}
};
Tooltip.prototype.applyPlacement = function (offset, placement) {
var $tip = this.tip();
var width = $tip[0].offsetWidth;
var height = $tip[0].offsetHeight;
// manually read margins because getBoundingClientRect includes difference
var marginTop = parseInt($tip.css('margin-top'), 10);
var marginLeft = parseInt($tip.css('margin-left'), 10);
// we must check for NaN for ie 8/9
if (isNaN(marginTop)) marginTop = 0;
if (isNaN(marginLeft)) marginLeft = 0;
offset.top = offset.top + marginTop;
offset.left = offset.left + marginLeft;
// $.fn.offset doesn't round pixel values
// so we use setOffset directly with our own function B-0
$.offset.setOffset($tip[0], $.extend({
using: function (props) {
$tip.css({
top: Math.round(props.top),
left: Math.round(props.left)
});
}
}, offset), 0);
$tip.addClass('in');
// check to see if placing tip in new offset caused the tip to resize itself
var actualWidth = $tip[0].offsetWidth;
var actualHeight = $tip[0].offsetHeight;
if (placement == 'top' && actualHeight != height) {
offset.top = offset.top + height - actualHeight;
}
var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight);
if (delta.left) offset.left += delta.left;
else offset.top += delta.top;
var isVertical = /top|bottom/.test(placement);
var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight;
var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight';
$tip.offset(offset);
this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical);
};
Tooltip.prototype.replaceArrow = function (delta, dimension, isHorizontal) {
this.arrow()
.css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
.css(isHorizontal ? 'top' : 'left', '');
};
Tooltip.prototype.setContent = function () {
var $tip = this.tip();
var title = this.getTitle();
$tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title);
$tip.removeClass('fade in top bottom left right');
};
Tooltip.prototype.hide = function (callback) {
var that = this;
var $tip = this.tip();
var e = $.Event('hide.bs.' + this.type);
function complete() {
if (that.hoverState != 'in') $tip.detach();
that.$element
.removeAttr('aria-describedby')
.trigger('hidden.bs.' + that.type);
callback && callback();
}
this.$element.trigger(e);
if (e.isDefaultPrevented()) return;
$tip.removeClass('in');
$.support.transition && this.$tip.hasClass('fade') ?
$tip
.one('bsTransitionEnd', complete)
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
complete();
this.hoverState = null;
return this;
};
Tooltip.prototype.fixTitle = function () {
var $e = this.$element;
if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {
$e.attr('data-original-title', $e.attr('title') || '').attr('title', '');
}
};
Tooltip.prototype.hasContent = function () {
return this.getTitle();
};
Tooltip.prototype.getPosition = function ($element) {
$element = $element || this.$element;
var el = $element[0];
var isBody = el.tagName == 'BODY';
var elRect = el.getBoundingClientRect();
if (elRect.width == null) {
// width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top });
}
var elOffset = isBody ? { top: 0, left: 0 } : $element.offset();
var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() };
var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null;
return $.extend({}, elRect, scroll, outerDims, elOffset);
};
Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
/* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width };
};
Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
var delta = { top: 0, left: 0 };
if (!this.$viewport) return delta;
var viewportPadding = this.options.viewport && this.options.viewport.padding || 0;
var viewportDimensions = this.getPosition(this.$viewport);
if (/right|left/.test(placement)) {
var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll;
var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight;
if (topEdgeOffset < viewportDimensions.top) { // top overflow
delta.top = viewportDimensions.top - topEdgeOffset;
} else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset;
}
} else {
var leftEdgeOffset = pos.left - viewportPadding;
var rightEdgeOffset = pos.left + viewportPadding + actualWidth;
if (leftEdgeOffset < viewportDimensions.left) { // left overflow
delta.left = viewportDimensions.left - leftEdgeOffset;
} else if (rightEdgeOffset > viewportDimensions.width) { // right overflow
delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset;
}
}
return delta;
};
Tooltip.prototype.getTitle = function () {
var title;
var $e = this.$element;
var o = this.options;
title = $e.attr('data-original-title')
|| (typeof o.title == 'function' ? o.title.call($e[0]) : o.title);
return title;
};
Tooltip.prototype.getUID = function (prefix) {
do prefix += ~~(Math.random() * 1000000);
while (document.getElementById(prefix));
return prefix;
};
Tooltip.prototype.tip = function () {
return (this.$tip = this.$tip || $(this.options.template));
};
Tooltip.prototype.arrow = function () {
return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'));
};
Tooltip.prototype.enable = function () {
this.enabled = true;
};
Tooltip.prototype.disable = function () {
this.enabled = false;
};
Tooltip.prototype.toggleEnabled = function () {
this.enabled = !this.enabled;
};
Tooltip.prototype.toggle = function (e) {
var self = this;
if (e) {
self = $(e.currentTarget).data('bs.' + this.type);
if (!self) {
self = new this.constructor(e.currentTarget, this.getDelegateOptions());
$(e.currentTarget).data('bs.' + this.type, self);
}
}
self.tip().hasClass('in') ? self.leave(self) : self.enter(self);
};
Tooltip.prototype.destroy = function () {
var that = this;
clearTimeout(this.timeout);
this.hide(function () {
that.$element.off('.' + that.type).removeData('bs.' + that.type);
});
};
// TOOLTIP PLUGIN DEFINITION
// =========================
function Plugin(option) {
return this.each(function () {
var $this = $(this);
var data = $this.data('bs.tooltip');
var options = typeof option == 'object' && option;
var selector = options && options.selector;
if (!data && option == 'destroy') return;
if (selector) {
if (!data) $this.data('bs.tooltip', (data = {}));
if (!data[selector]) data[selector] = new Tooltip(this, options);
} else {
if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)));
}
if (typeof option == 'string') data[option]();
});
}
var old = $.fn.tooltip;
$.fn.tooltip = Plugin;
$.fn.tooltip.Constructor = Tooltip;
// TOOLTIP NO CONFLICT
// ===================
$.fn.tooltip.noConflict = function () {
$.fn.tooltip = old;
return this;
};
}(jQuery);
/* ========================================================================
* Bootstrap: popover.js v3.3.1
* http://getbootstrap.com/javascript/#popovers
* ========================================================================
* Copyright 2011-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
if(typeof $().popover == 'function')
return true;
// POPOVER PUBLIC CLASS DEFINITION
// ===============================
var Popover = function (element, options) {
this.init('popover', element, options);
};
if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js');
Popover.VERSION = '3.3.1';
Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
placement: 'right',
trigger: 'click',
content: '',
template: '
'
});
// NOTE: POPOVER EXTENDS tooltip.js
// ================================
Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype);
Popover.prototype.constructor = Popover;
Popover.prototype.getDefaults = function () {
return Popover.DEFAULTS;
};
Popover.prototype.setContent = function () {
var $tip = this.tip();
var title = this.getTitle();
var content = this.getContent();
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title);
$tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
](content);
$tip.removeClass('fade top bottom left right in');
// IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
// this manually by checking the contents.
if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide();
};
Popover.prototype.hasContent = function () {
return this.getTitle() || this.getContent();
};
Popover.prototype.getContent = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-content')
|| (typeof o.content == 'function' ?
o.content.call($e[0]) :
o.content);
};
Popover.prototype.arrow = function () {
return (this.$arrow = this.$arrow || this.tip().find('.arrow'));
};
Popover.prototype.tip = function () {
if (!this.$tip) this.$tip = $(this.options.template);
return this.$tip;
};
// POPOVER PLUGIN DEFINITION
// =========================
function Plugin(option) {
return this.each(function () {
var $this = $(this);
var data = $this.data('bs.popover');
var options = typeof option == 'object' && option;
var selector = options && options.selector;
if (!data && option == 'destroy') return;
if (selector) {
if (!data) $this.data('bs.popover', (data = {}));
if (!data[selector]) data[selector] = new Popover(this, options);
} else {
if (!data) $this.data('bs.popover', (data = new Popover(this, options)));
}
if (typeof option == 'string') data[option]();
});
}
var old = $.fn.popover;
$.fn.popover = Plugin;
$.fn.popover.Constructor = Popover;
// POPOVER NO CONFLICT
// ===================
$.fn.popover.noConflict = function () {
$.fn.popover = old;
return this;
};
}(jQuery);
}
function chart_title(args) {
'use strict';
var container = d3.select(args.target);
// remove the current title if it exists
container.select('.mg-chart-title').remove();
if (args.target && args.title) {
//only show question mark if there's a description
var optional_question_mark = (args.show_tooltips && args.description)
? ''
: '';
container.insert('h2', ':first-child')
.attr('class', 'mg-chart-title')
.html(args.title + optional_question_mark);
//activate the question mark if we have a description
if (args.show_tooltips && args.description) {
var $newTitle = $(container.node()).find('h2.mg-chart-title');
$newTitle.popover({
html: true,
animation: false,
content: args.description,
trigger: 'hover',
placement: 'top',
container: $newTitle
});
}
}
if (args.error) {
error(args);
}
}
function y_rug(args) {
'use strict';
var svg = mg_get_svg_child_of(args.target);
var buffer_size = args.chart_type === 'point'
? args.buffer / 2
: args.buffer * 2 / 3;
var all_data = [];
for (var i = 0; i < args.data.length; i++) {
for (var j = 0; j < args.data[i].length; j++) {
all_data.push(args.data[i][j]);
}
}
var rug = svg.selectAll('line.mg-y-rug').data(all_data);
//set the attributes that do not change after initialization, per
//D3's general update pattern
rug.enter().append('svg:line')
.attr('class', 'mg-y-rug')
.attr('opacity', 0.3);
//remove rug elements that are no longer in use
rug.exit().remove();
//set coordinates of new rug elements
rug.exit().remove();
rug.attr('x1', args.left + 1)
.attr('x2', args.left+buffer_size)
.attr('y1', args.scalefns.yf)
.attr('y2', args.scalefns.yf);
if (args.color_accessor) {
rug.attr('stroke', args.scalefns.color);
rug.classed('mg-y-rug-mono', false);
} else {
rug.attr('stroke', null);
rug.classed('mg-y-rug-mono', true);
}
}
function y_axis(args) {
if (!args.processed) {
args.processed = {};
}
var svg = mg_get_svg_child_of(args.target);
var g;
var min_y,
max_y;
args.scalefns.yf = function(di) {
//since we want to show actual zeros when missing_is_hidden is on
if(args.missing_is_hidden && di['missing']) {
return args.scales.Y(di[args.y_accessor]) + 42.1234;
}
return args.scales.Y(di[args.y_accessor]);
};
var _set = false,
gtZeroFilter = function(d) { return d[args.y_accessor] > 0; },
mapToY = function(d) { return d[args.y_accessor]; };
for (var i = 0; i < args.data.length; i++) {
var a = args.data[i];
if (args.y_scale_type === 'log') {
// filter positive values
a = a.filter(gtZeroFilter);
}
if (a.length > 0) { // get min/max in one pass
var extent = d3.extent(a, mapToY);
if (!_set) {
// min_y and max_y haven't been set
min_y = extent[0];
max_y = extent[1];
_set = true;
} else {
min_y = Math.min(extent[0], min_y);
max_y = Math.max(extent[1], max_y);
}
}
}
// the default case is for the y-axis to start at 0, unless we explicitly want it
// to start at an arbitrary number or from the data's minimum value
if (min_y >= 0 && !args.min_y && !args.min_y_from_data) {
min_y = 0;
}
if (args.chart_type === 'bar') {
min_y = 0;
max_y = d3.max(args.data[0], function(d) {
var trio = [];
trio.push(d[args.y_accessor]);
if (args.baseline_accessor !== null) {
trio.push(d[args.baseline_accessor]);
}
if (args.predictor_accessor !== null) {
trio.push(d[args.predictor_accessor]);
}
return Math.max.apply(null, trio);
});
}
//if a min_y or max_y have been set, use those instead
min_y = args.min_y !== null ? args.min_y : min_y;
max_y = args.max_y !== null ? args.max_y : max_y * args.inflator;
if (args.y_scale_type !== 'log') {
//we are currently saying that if the min val > 0, set 0 as min y
if (min_y >= 0) {
args.y_axis_negative = false;
} else {
min_y = min_y - (max_y * (args.inflator - 1));
args.y_axis_negative = true;
}
}
if (!args.min_y && args.min_y_from_data) {
min_y = min_y / args.inflator;
}
if (args.y_scale_type === 'log') {
if (args.chart_type === 'histogram') {
// log histogram plots should start just below 1
// so that bins with single counts are visible
min_y = 0.2;
} else {
if (min_y <= 0) {
min_y = 1;
}
}
args.scales.Y = d3.scale.log()
.domain([min_y, max_y])
.range([args.height - args.bottom - args.buffer, args.top])
.clamp(true);
} else {
args.scales.Y = d3.scale.linear()
.domain([min_y, max_y])
.range([args.height - args.bottom - args.buffer, args.top]);
}
args.processed.min_y = min_y;
args.processed.max_y = max_y;
//used for ticks and such, and designed to be paired with log or linear
args.scales.Y_axis = d3.scale.linear()
.domain([args.processed.min_y, args.processed.max_y])
.range([args.height - args.bottom - args.buffer, args.top]);
var yax_format = args.yax_format;
if (!yax_format) {
if (args.format === 'count') {
yax_format = function(f) {
if (f < 1.0) {
// Don't scale tiny values.
return args.yax_units + d3.round(f, args.decimals);
} else {
var pf = d3.formatPrefix(f);
return args.yax_units + pf.scale(f) + pf.symbol;
}
};
} else { //percentage
yax_format = function(d_) {
var n = d3.format('%p');
return n(d_);
};
}
}
//remove the old y-axis, add new one
svg.selectAll('.mg-y-axis').remove();
if (!args.y_axis) {
return this;
}
//y axis
g = svg.append('g')
.classed('mg-y-axis', true)
.classed('mg-y-axis-small', args.use_small_class);
//are we adding a label?
if (args.y_label) {
g.append('text')
.attr('class', 'label')
.attr('x', function() {
return -1 * (args.top + args.buffer +
((args.height - args.bottom - args.buffer)
- (args.top + args.buffer)) / 2);
})
.attr('y', function() {
return args.left / 2;
})
.attr("dy", "0.4em")
.attr('text-anchor', 'middle')
.text(function(d) {
return args.y_label;
})
.attr("transform", function(d) {
return "rotate(-90)";
});
}
var scale_ticks = args.scales.Y.ticks(args.yax_count);
function log10(val) {
if (val === 1000) {
return 3;
}
if (val === 1000000) {
return 7;
}
return Math.log(val) / Math.LN10;
}
if (args.y_scale_type === 'log') {
// get out only whole logs
scale_ticks = scale_ticks.filter(function(d) {
return Math.abs(log10(d)) % 1 < 1e-6 || Math.abs(log10(d)) % 1 > 1-1e-6;
});
}
//filter out fraction ticks if our data is ints and if ymax > number of generated ticks
var number_of_ticks = args.scales.Y.ticks(args.yax_count).length;
//is our data object all ints?
var data_is_int = true;
args.data.forEach(function(d, i) {
d.forEach(function(d, i) {
if (d[args.y_accessor] % 1 !== 0) {
data_is_int = false;
return false;
}
});
});
if (data_is_int && number_of_ticks > max_y && args.format === 'count') {
//remove non-integer ticks
scale_ticks = scale_ticks.filter(function(d) {
return d % 1 === 0;
});
}
var last_i = scale_ticks.length - 1;
if (!args.x_extended_ticks && !args.y_extended_ticks) {
g.append('line')
.attr('x1', args.left)
.attr('x2', args.left)
.attr('y1', args.scales.Y(scale_ticks[0]).toFixed(2))
.attr('y2', args.scales.Y(scale_ticks[last_i]).toFixed(2));
}
//add y ticks
g.selectAll('.mg-yax-ticks')
.data(scale_ticks).enter()
.append('line')
.classed('mg-extended-y-ticks', args.y_extended_ticks)
.attr('x1', args.left)
.attr('x2', function() {
return (args.y_extended_ticks)
? args.width - args.right
: args.left - args.yax_tick_length;
})
.attr('y1', function(d) { return args.scales.Y(d).toFixed(2); })
.attr('y2', function(d) { return args.scales.Y(d).toFixed(2); });
g.selectAll('.mg-yax-labels')
.data(scale_ticks).enter()
.append('text')
.attr('x', args.left - args.yax_tick_length * 3 / 2)
.attr('dx', -3).attr('y', function(d) {
return args.scales.Y(d).toFixed(2);
})
.attr('dy', '.35em')
.attr('text-anchor', 'end')
.text(function(d) {
var o = yax_format(d);
return o;
});
if (args.y_rug) {
y_rug(args);
}
return this;
}
function y_axis_categorical(args) {
// first, come up with y_axis
args.scales.Y = d3.scale.ordinal()
.domain(args.categorical_variables)
.rangeRoundBands([args.height - args.bottom - args.buffer, args.top], args.padding_percentage, args.outer_padding_percentage);
args.scalefns.yf = function(di) {
return args.scales.Y(di[args.y_accessor]);
};
var svg = mg_get_svg_child_of(args.target);
//remove the old y-axis, add new one
svg.selectAll('.mg-y-axis').remove();
var g = svg.append('g')
.classed('mg-y-axis', true)
.classed('mg-y-axis-small', args.use_small_class);
if (!args.y_axis) {
return this;
}
g.selectAll('text').data(args.categorical_variables).enter().append('svg:text')
.attr('x', args.left)
.attr('y', function(d) {
return args.scales.Y(d) + args.scales.Y.rangeBand() / 2
+ (args.buffer)*args.outer_padding_percentage;
})
.attr('dy', '.35em')
.attr('text-anchor', 'end')
.text(String);
return this;
}
function x_rug(args) {
'use strict';
var buffer_size = args.chart_type === 'point'
? args.buffer / 2
: args.buffer;
var svg = mg_get_svg_child_of(args.target);
var all_data=[];
for (var i=0; i 10
? d3.scale.category20() : d3.scale.category10());
args.scales.color.domain(color_domain);
}
args.scalefns.color = function(di) {
return args.scales.color(di[args.color_accessor]);
};
}
}
function mg_point_add_size_scale(args) {
var min_size, max_size, size_domain, size_range;
if (args.size_accessor !== null) {
if (args.size_domain === null) {
min_size = d3.min(args.data[0], function(d) {
return d[args.size_accessor];
});
max_size = d3.max(args.data[0], function(d) {
return d[args.size_accessor];
});
size_domain = [min_size, max_size];
} else {
size_domain = args.size_domain;
}
if (args.size_range === null) {
size_range = [1,5]; //args.size_domain;
} else {
size_range = args.size_range;
}
args.scales.size = d3.scale.linear()
.domain(size_domain)
.range(size_range)
.clamp(true);
args.scalefns.size = function(di) {
return args.scales.size(di[args.size_accessor]);
};
}
}
function mg_add_x_label(g, args) {
g.append('text')
.attr('class', 'label')
.attr('x', function() {
return args.left + args.buffer
+ ((args.width - args.right - args.buffer)
- (args.left + args.buffer)) / 2;
})
.attr('y', (args.height - args.bottom / 2).toFixed(2))
.attr('dy', '.50em')
.attr('text-anchor', 'middle')
.text(function(d) {
return args.x_label;
});
}
function mg_default_bar_xax_format(args) {
if (args.xax_format) {
return args.xax_format;
}
return function(f) {
if (f < 1.0) {
//don't scale tiny values
return args.xax_units + d3.round(f, args.decimals);
} else {
var pf = d3.formatPrefix(f);
return args.xax_units + pf.scale(f) + pf.symbol;
}
};
}
function mg_default_xax_format(args) {
if (args.xax_format) {
return args.xax_format;
}
var diff,
main_time_format,
time_frame;
if (args.time_series) {
diff = (args.processed.max_x - args.processed.min_x) / 1000;
if (diff < 60) {
main_time_format = d3.time.format('%M:%S');
time_frame = 'seconds';
} else if (diff / (60 * 60) <= 24) {
main_time_format = d3.time.format('%H:%M');
time_frame = 'less-than-a-day';
} else if (diff / (60 * 60) <= 24 * 4) {
main_time_format = d3.time.format('%H:%M');
time_frame = 'four-days';
} else {
main_time_format = d3.time.format('%b %d');
time_frame = 'default';
}
}
args.processed.main_x_time_format = main_time_format;
args.processed.x_time_frame = time_frame;
return function(d) {
var df = d3.time.format('%b %d');
var pf = d3.formatPrefix(d);
// format as date or not, of course user can pass in
// a custom function if desired
if(args.data[0][0][args.x_accessor] instanceof Date) {
return args.processed.main_x_time_format(d);
} if (typeof args.data[0][0][args.x_accessor] === 'number') {
if (d < 1.0) {
//don't scale tiny values
return args.xax_units + d3.round(d, args.decimals);
} else {
pf = d3.formatPrefix(d);
return args.xax_units + pf.scale(d) + pf.symbol;
}
} else {
return d;
}
};
}
function mg_add_x_ticks(g, args) {
var last_i = args.scales.X.ticks(args.xax_count).length - 1;
if (args.chart_type !== 'bar' && !args.x_extended_ticks && !args.y_extended_ticks) {
//extend axis line across bottom, rather than from domain's min..max
g.append('line')
.attr('x1',
(args.concise === false || args.xax_count === 0)
? args.left + args.buffer
: (args.scales.X(args.scales.X.ticks(args.xax_count)[0])).toFixed(2)
)
.attr('x2',
(args.concise === false || args.xax_count === 0)
? args.width - args.right - args.buffer
: (args.scales.X(args.scales.X.ticks(args.xax_count)[last_i])).toFixed(2)
)
.attr('y1', args.height - args.bottom)
.attr('y2', args.height - args.bottom);
}
g.selectAll('.mg-xax-ticks')
.data(args.scales.X.ticks(args.xax_count)).enter()
.append('line')
.attr('x1', function(d) { return args.scales.X(d).toFixed(2); })
.attr('x2', function(d) { return args.scales.X(d).toFixed(2); })
.attr('y1', args.height - args.bottom)
.attr('y2', function() {
return (args.x_extended_ticks)
? args.top
: args.height - args.bottom + args.xax_tick_length;
})
.attr('class', function() {
if (args.x_extended_ticks) {
return 'mg-extended-x-ticks';
}
});
}
function mg_add_x_tick_labels(g, args) {
g.selectAll('.mg-xax-labels')
.data(args.scales.X.ticks(args.xax_count)).enter()
.append('text')
.attr('x', function(d) { return args.scales.X(d).toFixed(2); })
.attr('y', (args.height - args.bottom + args.xax_tick_length * 7 / 3).toFixed(2))
.attr('dy', '.50em')
.attr('text-anchor', 'middle')
.text(function(d) {
return args.xax_units + args.xax_format(d);
});
if (args.time_series && (args.show_years || args.show_secondary_x_label)) {
var secondary_marks,
secondary_function, yformat;
var time_frame = args.processed.x_time_frame;
switch(time_frame) {
case 'seconds':
secondary_function = d3.time.days;
yformat = d3.time.format('%I %p');
break;
case 'less-than-a-day':
secondary_function = d3.time.days;
yformat = d3.time.format('%b %d');
break;
case 'four-days':
secondary_function = d3.time.days;
yformat = d3.time.format('%b %d');
break;
default:
secondary_function = d3.time.years;
yformat = d3.time.format('%Y');
}
var years = secondary_function(args.processed.min_x, args.processed.max_x);
if (years.length === 0) {
var first_tick = args.scales.X.ticks(args.xax_count)[0];
years = [first_tick];
}
//append year marker to x-axis group
g = g.append('g')
.classed('mg-year-marker', true)
.classed('mg-year-marker-small', args.use_small_class);
if (time_frame === 'default') {
g.selectAll('.mg-year-marker')
.data(years).enter()
.append('line')
.attr('x1', function(d) { return args.scales.X(d).toFixed(2); })
.attr('x2', function(d) { return args.scales.X(d).toFixed(2); })
.attr('y1', args.top)
.attr('y2', args.height - args.bottom);
}
g.selectAll('.mg-year-marker')
.data(years).enter()
.append('text')
.attr('x', function(d) { return args.scales.X(d).toFixed(2); })
.attr('y', (args.height - args.bottom + args.xax_tick_length * 7 / 1.3).toFixed(2))
.attr('dy', args.use_small_class ? -3 : 0)//(args.y_extended_ticks) ? 0 : 0 )
.attr('text-anchor', 'middle')
.text(function(d) {
return yformat(d);
});
}
}
function mg_find_min_max_x(args) {
var last_i,
extent_x = [],
min_x,
max_x,
all_data = [].concat.apply([], args.data),
mapDtoX = function(d) { return d[args.x_accessor]; };
// clear the cached xax_format in case we need to recalculate
if(args.xax_format === null) {
delete args.xax_format;
}
if (args.chart_type === 'line' || args.chart_type === 'point' || args.chart_type === 'histogram') {
extent_x = d3.extent(all_data, mapDtoX);
min_x = extent_x[0];
max_x = extent_x[1];
} else if (args.chart_type === 'bar') {
min_x = 0;
max_x = d3.max(all_data, function(d) {
var trio = [
d[args.x_accessor],
(d[args.baseline_accessor]) ? d[args.baseline_accessor] : 0,
(d[args.predictor_accessor]) ? d[args.predictor_accessor] : 0
];
return Math.max.apply(null, trio);
});
}
//if data set is of length 1, expand the range so that we can build the x-axis
//of course, a line chart doesn't make sense in this case, so the preferred
//method would be to check for said object's length and, if appropriate,
//change the chart type to 'point'
if (min_x === max_x) {
if (min_x instanceof Date) {
var yesterday = MG.clone(min_x).setDate(min_x.getDate() - 1);
var tomorrow = MG.clone(min_x).setDate(min_x.getDate() + 1);
min_x = yesterday;
max_x = tomorrow;
} else if (typeof min_x === 'number') {
min_x = min_x - 1;
max_x = max_x + 1;
} else if (typeof min_x === 'string') {
min_x = Number(min_x) - 1;
max_x = Number(max_x) + 1;
}
//force xax_count to be 2
args.xax_count = 2;
}
min_x = args.min_x ? args.min_x : min_x;
max_x = args.max_x ? args.max_x : max_x;
args.x_axis_negative = false;
args.processed.min_x = min_x;
args.processed.max_x = max_x;
mg_select_xax_format(args);
if (!args.time_series) {
if (args.processed.min_x < 0) {
args.processed.min_x = args.processed.min_x - (args.processed.max_x * (args.inflator - 1));
args.x_axis_negative = true;
}
}
if (args.chart_type === 'bar') {
args.additional_buffer = args.buffer * 5;
} else {
args.additional_buffer = 0;
}
}
function mg_select_xax_format(args) {
if (!args.xax_format && args.chart_type === 'line') args.xax_format = mg_default_xax_format(args);
if (!args.xax_format && args.chart_type === 'point') args.xax_format = mg_default_xax_format(args);
if (!args.xax_format && args.chart_type === 'histogram') args.xax_format = mg_default_xax_format(args);
if (!args.xax_format && args.chart_type === 'bar') args.xax_format = mg_default_bar_xax_format(args);
}
function init(args) {
'use strict';
var defaults = {
target: null,
title: null,
description: null
};
args = arguments[0];
if (!args) { args = {}; }
args = merge_with_defaults(args, defaults);
if (d3.select(args.target).empty()) {
console.warn('The specified target element "' + args.target + '" could not be found in the page. The chart will not be rendered.');
return;
}
var container = d3.select(args.target);
var svg = container.selectAll('svg');
//this is how we're dealing with passing in a single array of data,
//but with the intention of using multiple values for multilines, etc.
//do we have a time_series?
if (args.data[0][0][args.x_accessor] instanceof Date) {
args.time_series = true;
} else {
args.time_series = false;
}
var svg_width = args.width;
var svg_height = args.height;
//are we setting the aspect ratio
if (args.full_width) {
// get parent element
svg_width = get_width(args.target);
}
if (args.full_height) {
svg_height = get_height(args.target);
}
if (args.chart_type === 'bar' && svg_height === null) {
svg_height = args.height = args.data[0].length * args.bar_height + args.top + args.bottom;
}
//remove the svg if the chart type has changed
if ((!svg.selectAll('.mg-main-line').empty() && args.chart_type !== 'line')
|| (!svg.selectAll('.mg-points').empty() && args.chart_type !== 'point')
|| (!svg.selectAll('.mg-histogram').empty() && args.chart_type !== 'histogram')
|| (!svg.selectAll('.mg-barplot').empty() && args.chart_type !== 'bar')
) {
svg.remove();
}
//add svg if it doesn't already exist
//using trim on html rather than :empty to ignore white spaces if they exist
if (mg_get_svg_child_of(args.target).empty()) {
//add svg
svg = d3.select(args.target)
.append('svg')
.classed('linked', args.linked)
.attr('width', svg_width)
.attr('height', svg_height);
}
args.width = svg_width;
args.height = svg_height;
// add clip path element to svg
svg.selectAll('.mg-clip-path').remove();
svg.append('defs')
.attr('class', 'mg-clip-path')
.append('clipPath')
.attr('id', 'mg-plot-window-' + mg_strip_punctuation(args.target))
.append('svg:rect')
.attr('x', args.left)
.attr('y', args.top)
.attr('width', args.width - args.left - args.right - args.buffer)
.attr('height', args.height - args.top - args.bottom - args.buffer + 1);
//has the width or height changed?
if (svg_width !== Number(svg.attr('width'))) {
svg.attr('width', svg_width);
}
if (svg_height !== Number(svg.attr('height'))) {
svg.attr('height', svg_height);
}
// This is an unfinished feature. Need to reconsider how we handle automatic scaling.
svg.attr('viewBox', '0 0 ' + svg_width + ' ' + svg_height);
if (args.full_width || args.full_height) {
svg.attr('preserveAspectRatio', 'xMinYMin meet');
}
// remove missing class
svg.classed('mg-missing', false);
// remove missing text
svg.selectAll('.mg-missing-text').remove();
svg.selectAll('.mg-missing-pane').remove();
//add chart title if it's different than existing one
chart_title(args);
//draw axes
args.use_small_class = args.height - args.top - args.bottom - args.buffer
<= args.small_height_threshold && args.width - args.left-args.right - args.buffer * 2
<= args.small_width_threshold || args.small_text;
//if we're updating an existing chart and we have fewer lines than
//before, remove the outdated lines, e.g. if we had 3 lines, and we're calling
//data_graphic() on the same target with 2 lines, remove the 3rd line
var i = 0;
if (args.data.length < svg.selectAll('.mg-main-line')[0].length) {
//now, the thing is we can't just remove, say, line3 if we have a custom
//line-color map, instead, see which are the lines to be removed, and delete those
if (args.custom_line_color_map.length > 0) {
var array_full_series = function(len) {
var arr = new Array(len);
for (var i = 0; i < arr.length; i++) { arr[i] = i + 1; }
return arr;
};
//get an array of lines ids to remove
var lines_to_remove = arrDiff(
array_full_series(args.max_data_size),
args.custom_line_color_map);
for (i = 0; i < lines_to_remove.length; i++) {
svg.selectAll('.mg-main-line.mg-line' + lines_to_remove[i] + '-color')
.remove();
}
}
//if we don't have a customer line-color map, just remove the lines from the end
else {
var num_of_new = args.data.length;
var num_of_existing = svg.selectAll('.mg-main-line')[0].length;
for (i = num_of_existing; i > num_of_new; i--) {
svg.selectAll('.mg-main-line.mg-line' + i + '-color')
.remove();
}
}
}
return this;
}
function markers(args) {
'use strict';
var svg = mg_get_svg_child_of(args.target);
var gm;
var gb;
//remove existing markers and baselines
svg.selectAll('.mg-markers').remove();
svg.selectAll('.mg-baselines').remove();
if (args.markers) {
gm = svg.append('g')
.attr('class', 'mg-markers');
gm.selectAll('.mg-markers')
.data(args.markers.filter(inRange))
.enter()
.append('line')
.attr('x1', xPositionFixed)
.attr('x2', xPositionFixed)
.attr('y1', args.top)
.attr('y2', function() {
return args.height - args.bottom - args.buffer;
})
.attr('stroke-dasharray', '3,1');
gm.selectAll('.mg-markers')
.data(args.markers.filter(inRange))
.enter()
.append('text')
.attr('class', 'mg-marker-text')
.attr('x', xPosition)
.attr('y', args.top - 8)
.attr('text-anchor', 'middle')
.text(function(d) {
return d.label;
});
preventOverlap(gm.selectAll('.mg-marker-text'));
}
if (args.baselines) {
gb = svg.append('g')
.attr('class', 'mg-baselines');
gb.selectAll('.mg-baselines')
.data(args.baselines)
.enter().append('line')
.attr('x1', args.left + args.buffer)
.attr('x2', args.width-args.right-args.buffer)
.attr('y1', function(d){
return args.scales.Y(d.value).toFixed(2);
})
.attr('y2', function(d){
return args.scales.Y(d.value).toFixed(2);
});
gb.selectAll('.mg-baselines')
.data(args.baselines)
.enter().append('text')
.attr('x', args.width-args.right - args.buffer)
.attr('y', function(d){
return args.scales.Y(d.value).toFixed(2);
})
.attr('dy', -3)
.attr('text-anchor', 'end')
.text(function(d) {
return d.label;
});
}
function preventOverlap (labels) {
var prev;
labels.each(function(d, i) {
if (i > 0) {
var thisbb = this.getBoundingClientRect();
if (isOverlapping(this, labels)) {
var node = d3.select(this), newY = +node.attr('y');
if (newY + 8 == args.top) {
newY = args.top - 16;
}
node.attr('y', newY);
}
}
prev = this;
});
}
function isOverlapping(element, labels) {
var bbox = element.getBoundingClientRect();
for(var i = 0; i < labels.length; i++) {
var elbb = labels[0][i].getBoundingClientRect();
if (
labels[0][i] !== element &&
((elbb.right > bbox.left && elbb.left > bbox.left && bbox.top === elbb.top) ||
(elbb.left < bbox.left && elbb.right > bbox.left && bbox.top === elbb.top))
) return true;
}
return false;
}
function xPosition (d) {
return args.scales.X(d[args.x_accessor]);
}
function xPositionFixed (d) {
return xPosition(d).toFixed(2);
}
function inRange (d) {
return (args.scales.X(d[args.x_accessor]) > args.buffer + args.left)
&& (args.scales.X(d[args.x_accessor]) < args.width - args.buffer - args.right);
}
return this;
}
function mg_window_listeners(args){
mg_if_aspect_ratio_resize_svg(args);
}
function mg_if_aspect_ratio_resize_svg(args){
// If we've asked the svg to fill a div, resize with div.
if (args.full_width || args.full_height){
window.addEventListener('resize', function(){
var svg = d3.select(args.target).select('svg');
var aspect = svg.attr('height') / svg.attr('width');
var newWidth = get_width(args.target);
svg.attr('width', newWidth);
svg.attr('height', aspect * newWidth);
}, true);
}
}
if (typeof jQuery !== 'undefined') {
/*!
* Bootstrap v3.3.1 (http://getbootstrap.com)
* Copyright 2011-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*!
* Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=c3834cc5b59ef727da53)
* Config saved to config.json and https://gist.github.com/c3834cc5b59ef727da53
*/
/* ========================================================================
* Bootstrap: dropdown.js v3.3.1
* http://getbootstrap.com/javascript/#dropdowns
* ========================================================================
* Copyright 2011-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
if(typeof $().dropdown == 'function')
return true;
// DROPDOWN CLASS DEFINITION
// =========================
var backdrop = '.dropdown-backdrop';
var toggle = '[data-toggle="dropdown"]';
var Dropdown = function (element) {
$(element).on('click.bs.dropdown', this.toggle);
};
Dropdown.VERSION = '3.3.1';
Dropdown.prototype.toggle = function (e) {
var $this = $(this);
if ($this.is('.disabled, :disabled')) return;
var $parent = getParent($this);
var isActive = $parent.hasClass('open');
clearMenus();
if (!isActive) {
if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
// if mobile we use a backdrop because click events don't delegate
$('').insertAfter($(this)).on('click', clearMenus);
}
var relatedTarget = { relatedTarget: this };
$parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget));
if (e.isDefaultPrevented()) return;
$this
.trigger('focus')
.attr('aria-expanded', 'true');
$parent
.toggleClass('open')
.trigger('shown.bs.dropdown', relatedTarget);
}
return false;
};
Dropdown.prototype.keydown = function (e) {
if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return;
var $this = $(this);
e.preventDefault();
e.stopPropagation();
if ($this.is('.disabled, :disabled')) return;
var $parent = getParent($this);
var isActive = $parent.hasClass('open');
if ((!isActive && e.which != 27) || (isActive && e.which == 27)) {
if (e.which == 27) $parent.find(toggle).trigger('focus');
return $this.trigger('click');
}
var desc = ' li:not(.divider):visible a';
var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc);
if (!$items.length) return;
var index = $items.index(e.target);
if (e.which == 38 && index > 0) index--; // up
if (e.which == 40 && index < $items.length - 1) index++; // down
if (!~index) index = 0;
$items.eq(index).trigger('focus');
};
function clearMenus(e) {
if (e && e.which === 3) return;
$(backdrop).remove();
$(toggle).each(function () {
var $this = $(this);
var $parent = getParent($this);
var relatedTarget = { relatedTarget: this };
if (!$parent.hasClass('open')) return;
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget));
if (e.isDefaultPrevented()) return;
$this.attr('aria-expanded', 'false');
$parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget);
});
}
function getParent($this) {
var selector = $this.attr('data-target');
if (!selector) {
selector = $this.attr('href');
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, ''); // strip for ie7
}
var $parent = selector && $(selector);
return $parent && $parent.length ? $parent : $this.parent();
}
// DROPDOWN PLUGIN DEFINITION
// ==========================
function Plugin(option) {
return this.each(function () {
var $this = $(this);
var data = $this.data('bs.dropdown');
if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)));
if (typeof option == 'string') data[option].call($this);
});
}
var old = $.fn.dropdown;
$.fn.dropdown = Plugin;
$.fn.dropdown.Constructor = Dropdown;
// DROPDOWN NO CONFLICT
// ====================
$.fn.dropdown.noConflict = function () {
$.fn.dropdown = old;
return this;
};
// APPLY TO STANDARD DROPDOWN ELEMENTS
// ===================================
$(document)
.on('click.bs.dropdown.data-api', clearMenus)
.on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation(); })
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
.on('keydown.bs.dropdown.data-api', '[role="menu"]', Dropdown.prototype.keydown)
.on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown);
}(jQuery);
}
MG.button_layout = function(target) {
'use strict';
this.target = target;
this.feature_set = {};
this.public_name = {};
this.sorters = {};
this.manual = [];
this.manual_map = {};
this.manual_callback = {};
this._strip_punctuation = function(s) {
var punctuationless = s.replace(/[^a-zA-Z0-9 _]+/g, '');
var finalString = punctuationless.replace(/ +?/g, "");
return finalString;
};
this.data = function(data) {
this._data = data;
return this;
};
this.manual_button = function(feature, feature_set, callback) {
this.feature_set[feature]=feature_set;
this.manual_map[this._strip_punctuation(feature)] = feature;
this.manual_callback[feature]=callback;// the default is going to be the first feature.
return this;
};
this.button = function(feature) {
if (arguments.length > 1) {
this.public_name[feature] = arguments[1];
}
if (arguments.length > 2) {
this.sorters[feature] = arguments[2];
}
this.feature_set[feature] = [];
return this;
};
this.callback = function(callback) {
this._callback = callback;
return this;
};
this.display = function() {
var callback = this._callback;
var manual_callback = this.manual_callback;
var manual_map = this.manual_map;
var d,f, features, feat;
features = Object.keys(this.feature_set);
var mapDtoF = function(f) { return d[f]; };
var i;
// build out this.feature_set with this.data
for (i = 0; i < this._data.length; i++) {
d = this._data[i];
f = features.map(mapDtoF);
for (var j = 0; j < features.length; j++) {
feat = features[j];
if (this.feature_set[feat].indexOf(f[j]) === -1) {
this.feature_set[feat].push(f[j]);
}
}
}
for (feat in this.feature_set) {
if (this.sorters.hasOwnProperty(feat)) {
this.feature_set[feat].sort(this.sorters[feat]);
}
}
$(this.target).empty();
$(this.target).append("");
var dropdownLiAClick = function() {
var k = $(this).data('key');
var feature = $(this).data('feature');
var manual_feature;
$('.' + feature + '-btns button.btn span.title').html(k);
if (!manual_map.hasOwnProperty(feature)) {
callback(feature, k);
} else {
manual_feature = manual_map[feature];
manual_callback[manual_feature](k);
}
return false;
};
for (var feature in this.feature_set) {
features = this.feature_set[feature];
$(this.target + ' div.segments').append(
'