diff --git a/dist/metricsgraphics.js b/dist/metricsgraphics.js
new file mode 100644
index 0000000000..d7b1870b0d
--- /dev/null
+++ b/dist/metricsgraphics.js
@@ -0,0 +1,5050 @@
+(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
+ xax_start_at_min: false,
+ 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_year_markers: false,
+ show_secondary_x_label: true,
+ target: '#viz',
+ interpolate: 'cardinal', // interpolation method to use when rendering lines
+ interpolate_tension: 0.7, // its range is from 0 to 1; increase if your data is irregular and you notice artifacts
+ 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.width - args.right) / 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);
+ } else 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;
+ var ticks = args.scales.X.ticks(args.xax_count);
+
+ //force min to be the first tick rather than the first element in ticks
+ if(args.xax_start_at_min) {
+ ticks[0] = args.processed.min_x;
+ }
+
+ 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', function() {
+ //start the axis line from the beginning, domain's min, or the auto-generated
+ //ticks' first element, depending on whether xax_count is set to 0 or
+ //xax_start_at_min is set to true
+ if (args.xax_count === 0) {
+ return args.left + args.buffer;
+ }
+ else if (args.xax_start_at_min) {
+ return args.scales.X(args.processed.min_x).toFixed(2)
+ }
+ else {
+ return (args.scales.X(args.scales.X.ticks(args.xax_count)[0])).toFixed(2);
+ }
+ })
+ .attr('x2',
+ (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(ticks).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) {
+ var ticks = args.scales.X.ticks(args.xax_count);
+
+ //force min to be the first tick rather than the first element in ticks
+ if(args.xax_start_at_min) {
+ ticks[0] = args.processed.min_x;
+ }
+
+ g.selectAll('.mg-xax-labels')
+ .data(ticks).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 xax_start_at_min is set
+ if (args.xax_start_at_min && years.length === 0) {
+ var first_tick = ticks[0];
+ years = [first_tick];
+ } else 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' && args.show_year_markers) {
+ 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, i) {
+ if (args.xax_start_at_min && i == 0) {
+ d = ticks[0];
+ }
+
+ 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)
+ .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
+ };
+
+ // If you pass in a dom element for args.target, the expectation
+ // of a string elsewhere will break.
+
+ 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')[0]);
+ }
+
+ 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) {
+ if (!labels || labels.length == 1) {
+ return;
+ }
+
+ //see if each of our labels overlaps any of the other labels
+ for (var i = 0; i < labels.length; i++) {
+ //if so, nudge it up a bit, if the label it intersects hasn't already been nudged
+ if (isOverlapping(labels[i], labels)) {
+ var node = d3.select(labels[i]);
+ var newY = +node.attr('y');
+ if (newY + 8 == args.top) {
+ newY = args.top - 16;
+ }
+ node.attr('y', newY);
+ }
+ }
+ }
+
+ function isOverlapping(element, labels) {
+ var element_bbox = element.getBoundingClientRect();
+
+ for (var i = 0; i < labels.length; i++) {
+ if (labels[i] == element) {
+ continue;
+ }
+
+ //check to see if this label overlaps with any of the other labels
+ var sibling_bbox = labels[i].getBoundingClientRect();
+ if (element_bbox.top === sibling_bbox.top &&
+ !(sibling_bbox.left > element_bbox.right || sibling_bbox.right < element_bbox.left)
+ ) {
+ 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(
+ '' + // This never changes.
+ '' +
+ "" + (this.public_name.hasOwnProperty(feature) ? this.public_name[feature] : feature) +" " +
+ "" + (this.manual_callback.hasOwnProperty(feature) ? this.feature_set[feature][0] : 'all') + " " + // if a manual button, don't default to all in label.
+ ' ' +
+ ' ' +
+ ''
+ + '
');
+
+ for (i = 0; i < features.length; i++) {
+ if (features[i] !== 'all' && features[i] !== undefined) { // strange bug with undefined being added to manual buttons.
+ $(this.target + ' div.' + this._strip_punctuation(feature) + '-btns ul.dropdown-menu').append(
+ ''
+ + features[i] + ' '
+ );
+ }
+ }
+
+ $('.' + this._strip_punctuation(feature) + '-btns .dropdown-menu li a').on('click', dropdownLiAClick);
+ }
+
+ return this;
+ };
+
+ return this;
+ };
+
+ charts.line = function(args) {
+ 'use strict';
+ this.args = args;
+
+ this.init = function(args) {
+ raw_data_transformation(args);
+ process_line(args);
+ init(args);
+ x_axis(args);
+ y_axis(args);
+ return this;
+ };
+
+ this.mainPlot = function() {
+ var svg = mg_get_svg_child_of(args.target);
+ var g;
+ var data_median = 0;
+ var updateTransitionDuration = (args.transition_on_update) ? 1000 : 0;
+ var mapToY = function(d) {
+ return d[args.y_accessor];
+ };
+
+ //main area
+ var area = d3.svg.area()
+ .x(args.scalefns.xf)
+ .y0(args.scales.Y.range()[0])
+ .y1(args.scalefns.yf)
+ .interpolate(args.interpolate)
+ .tension(args.interpolate_tension);
+
+ //confidence band
+ var confidence_area;
+ var existing_band = svg.select('.mg-confidence-band');
+
+ if (args.show_confidence_band) {
+ confidence_area = d3.svg.area()
+ .x(args.scalefns.xf)
+ .y0(function(d) {
+ var l = args.show_confidence_band[0];
+ return args.scales.Y(d[l]);
+ })
+ .y1(function(d) {
+ var u = args.show_confidence_band[1];
+ return args.scales.Y(d[u]);
+ })
+ .interpolate(args.interpolate)
+ .tension(args.interpolate_tension);
+ }
+
+ //main line
+ var line = d3.svg.line()
+ .x(args.scalefns.xf)
+ .y(args.scalefns.yf)
+ .interpolate(args.interpolate)
+ .tension(args.interpolate_tension);
+
+ //for animating line on first load
+ var flat_line = d3.svg.line()
+ .x(args.scalefns.xf)
+ .y(function() { return args.scales.Y(data_median); })
+ .interpolate(args.interpolate)
+ .tension(args.interpolate_tension);
+
+
+ //for building the optional legend
+ var legend = '';
+ var this_data;
+ var confidenceBand;
+
+ for (var i = args.data.length - 1; i >= 0; i--) {
+ this_data = args.data[i];
+
+ //override increment if we have a custom increment series
+ var line_id = i + 1;
+ if (args.custom_line_color_map.length > 0) {
+ line_id = args.custom_line_color_map[i];
+ }
+
+ args.data[i].line_id = line_id;
+
+ //add confidence band
+ if (args.show_confidence_band) {
+ if (!existing_band.empty()) {
+ confidenceBand = existing_band
+ .transition()
+ .duration(function() {
+ return (args.transition_on_update) ? 1000 : 0;
+ });
+ } else {
+ confidenceBand = svg.append('path')
+ .attr('class', 'mg-confidence-band');
+ }
+
+ confidenceBand
+ .attr('d', confidence_area(args.data[i]))
+ .attr('clip-path', 'url(#mg-plot-window-'+ mg_strip_punctuation(args.target)+')');
+ }
+
+ //add the area
+ //var $area = $(args.target).find('svg path.mg-area' + (line_id) + '-color');
+ var areas = svg.selectAll('.mg-area' + (line_id) + '-color');
+ var displayArea = args.area && !args.use_data_y_min && !args.y_axis_negative && args.data.length <= 1;
+ if (displayArea) {
+ //if area already exists, transition it
+ if (!areas.empty()) {
+ //$(svg.node()).find('.mg-y-axis').after($area.detach());
+ svg.select('.mg-y-axis').node().parentNode.appendChild(areas.node());
+
+ areas
+ .transition()
+ .duration(updateTransitionDuration)
+ .attr('d', area(args.data[i]))
+ .attr('clip-path', 'url(#mg-plot-window-'+ mg_strip_punctuation(args.target)+')');
+ } else { //otherwise, add the area
+ svg.append('path')
+ .attr('class', 'mg-main-area ' + 'mg-area' + (line_id) + '-color')
+ .attr('d', area(args.data[i]))
+ .attr('clip-path', 'url(#mg-plot-window-' + mg_strip_punctuation(args.target) + ')');
+ }
+ } else if (!areas.empty()) {
+ areas.remove();
+ }
+
+ //add the line, if it already exists, transition the fine gentleman
+ var existing_line = svg.select('path.mg-main-line.mg-line' + (line_id) + '-color');
+ if (!existing_line.empty()) {
+ //$(svg.node()).find('.mg-y-axis').after($(existing_line.node()).detach());
+ svg.select('.mg-y-axis').node().parentNode.appendChild(existing_line.node());
+
+ var lineTransition = existing_line
+ .transition()
+ .duration(updateTransitionDuration);
+
+ if (!displayArea) {
+ lineTransition.attrTween('d', pathTween(line(args.data[i]), 4));
+ } else {
+ lineTransition.attr('d', line(args.data[i]));
+ }
+
+ }
+ else { //otherwise...
+ //if we're animating on load, animate the line from its median value
+ if (args.animate_on_load) {
+ data_median = d3.median(args.data[i], mapToY);
+
+ svg.append('path')
+ .attr('class', 'mg-main-line ' + 'mg-line' + (line_id) + '-color')
+ .attr('d', flat_line(args.data[i]))
+ .transition()
+ .duration(1000)
+ .attr('d', line(args.data[i]))
+ .attr('clip-path', 'url(#mg-plot-window-' + mg_strip_punctuation(args.target) + ')');
+ } else { //or just add the line
+ svg.append('path')
+ .attr('class', 'mg-main-line ' + 'mg-line' + (line_id) + '-color')
+ .attr('d', line(args.data[i]))
+ .attr('clip-path', 'url(#mg-plot-window-' + mg_strip_punctuation(args.target) + ')');
+ }
+ }
+
+ var the_line = svg.select('.mg-line' + (line_id) + '-color');
+ if (args.missing_is_hidden && the_line.attr('d') !== null) {
+ var bits = the_line.attr('d').split('L');
+ var zero = args.scales.Y(0) + 42.1234;
+ var dasharray = [];
+ var singleton_point_length = 2;
+
+ var x_y,
+ x_y_plus_1,
+ x,
+ y,
+ x_plus_1,
+ y_plus_1,
+ segment_length,
+ cumulative_segment_length = 0;
+
+ bits[0] = bits[0].replace('M', '');
+ bits[bits.length - 1] = bits[bits.length - 1].replace('Z', '');
+
+ //if we have a min_x, turn the line off first
+ if (args.min_x) {
+ dasharray.push(0);
+ }
+
+ //build the stroke-dasharray pattern
+ for (var j = 0; j < bits.length - 1; j++) {
+ x_y = bits[j].split(',');
+ x_y_plus_1 = bits[j + 1].split(',');
+ x = Number(x_y[0]);
+ y = Number(x_y[1]);
+ x_plus_1 = Number(x_y_plus_1[0]);
+ y_plus_1 = Number(x_y_plus_1[1]);
+
+ segment_length = Math.sqrt(Math.pow(x - x_plus_1, 2) + Math.pow(y - y_plus_1, 2));
+
+ //do we need to either cover or clear the current stroke
+ if (y_plus_1 == zero && y != zero) {
+ dasharray.push(cumulative_segment_length || singleton_point_length);
+ cumulative_segment_length = (cumulative_segment_length)
+ ? segment_length
+ : segment_length - singleton_point_length;
+ } else if (y_plus_1 != zero && y == zero) { //switching on line
+ dasharray.push(cumulative_segment_length += segment_length);
+ cumulative_segment_length = 0;
+ } else {
+ cumulative_segment_length += segment_length;
+ }
+ }
+
+ //fear not, end bit of line, ye too shall be covered
+ if (dasharray.length > 0) {
+ dasharray.push(the_line.node().getTotalLength() - dasharray[dasharray.length - 1]);
+
+ svg.select('.mg-line' + (line_id) + '-color')
+ .attr('stroke-dasharray', dasharray.join());
+ }
+ }
+
+ //build legend
+ if (args.legend) {
+ legend = "— "
+ + args.legend[i] + " " + legend;
+ }
+ }
+
+ if (args.legend) {
+ d3.select(args.legend_target).html(legend);
+ }
+
+ return this;
+ };
+
+ this.markers = function() {
+ markers(args);
+ return this;
+ };
+
+ this.rollover = function() {
+ var svg = mg_get_svg_child_of(args.target);
+ var g;
+
+ //remove the old rollovers if they already exist
+ svg.selectAll('.mg-rollover-rect').remove();
+ svg.selectAll('.mg-voronoi').remove();
+
+ //remove the old rollover text and circle if they already exist
+ svg.selectAll('.mg-active-datapoint').remove();
+ svg.selectAll('.mg-line-rollover-circle').remove();
+ svg.selectAll('.mg-active-datapoint-container').remove();
+
+ //rollover text
+ svg.append('g')
+ .attr('class', 'mg-active-datapoint-container')
+ .attr('transform', 'translate(' + (args.width - args.right) + ',' + (args.top / 2) + ')')
+ .append('text')
+ .attr('class', 'mg-active-datapoint')
+ .classed('mg-active-datapoint-small', args.use_small_class)
+ .attr('xml:space', 'preserve')
+ .attr('text-anchor', 'end');
+
+ //append circle
+ svg.selectAll('.mg-line-rollover-circle')
+ .data(args.data).enter()
+ .append('circle')
+ .attr({
+ 'class': function(d, i) {
+ return [
+ 'mg-line-rollover-circle',
+ 'mg-line' + d.line_id + '-color',
+ 'mg-area' + d.line_id + '-color'
+ ].join(' ');
+ },
+ 'cx': 0,
+ 'cy': 0,
+ 'r': 0
+ });
+
+ //update our data by setting a unique line id for each series
+ //increment from 1... unless we have a custom increment series
+ var line_id = 1;
+
+ for (var i = 0; i < args.data.length; i++) {
+ for (var j = 0; j < args.data[i].length; j++) {
+ //if custom line-color map is set, use that instead of line_id
+ if (args.custom_line_color_map.length > 0) {
+ args.data[i][j].line_id = args.custom_line_color_map[i];
+ } else {
+ args.data[i][j].line_id = line_id;
+ }
+ }
+ line_id++;
+ }
+
+ var data_nested;
+ var xf;
+
+ //for multi-line, use voronoi
+ if (args.data.length > 1 && !args.aggregate_rollover) {
+ //main rollover
+ var voronoi = d3.geom.voronoi()
+ .x(function(d) { return args.scales.X(d[args.x_accessor]).toFixed(2); })
+ .y(function(d) { return args.scales.Y(d[args.y_accessor]).toFixed(2); })
+ .clipExtent([[args.buffer, args.buffer], [args.width - args.buffer, args.height - args.buffer]]);
+
+ g = svg.append('g')
+ .attr('class', 'mg-voronoi');
+
+ //we'll be using these when constructing the voronoi rollovers
+ data_nested = d3.nest()
+ .key(function(d) {
+ return args.scales.X(d[args.x_accessor]) + ","
+ + args.scales.Y(d[args.y_accessor]);
+ })
+ .rollup(function(v) { return v[0]; })
+ .entries(d3.merge(args.data.map(function(d) { return d; })))
+ .map(function(d) { return d.values; });
+
+ //add the voronoi rollovers
+ g.selectAll('path')
+ .data(voronoi(data_nested))
+ .enter()
+ .append('path')
+ .filter(function(d) { return d !== undefined; })
+ .attr("d", function(d) { return "M" + d.join("L") + "Z"; })
+ .datum(function(d) { return d.point; }) //because of d3.nest, reassign d
+ .attr('class', function(d) {
+ if (args.linked) {
+ var v = d[args.x_accessor];
+ var formatter = d3.time.format(args.linked_format);
+
+ //only format when x-axis is date
+ var id = (typeof v === 'number')
+ ? i
+ : formatter(v);
+
+ return 'mg-line' + d.line_id + '-color ' + 'roll_' + id;
+ } else {
+ return 'mg-line' + d.line_id + '-color';
+ }
+ })
+ .on('mouseover', this.rolloverOn(args))
+ .on('mouseout', this.rolloverOff(args))
+ .on('mousemove', this.rolloverMove(args));
+ }
+
+ // for multi-lines and aggregated rollovers, use rects
+ else if (args.data.length > 1 && args.aggregate_rollover) {
+ data_nested = d3.nest()
+ .key(function(d) { return d[args.x_accessor]; })
+ .entries(d3.merge(args.data));
+
+ xf = data_nested.map(function(di) {
+ return args.scales.X(new Date(di.key));
+ });
+
+ g = svg.append('g')
+ .attr('class', 'mg-rollover-rect');
+
+ g.selectAll('.mg-rollover-rects')
+ .data(data_nested).enter()
+ .append('rect')
+ .attr('x', function(d, i) {
+ //if data set is of length 1
+ if(xf.length === 1) {
+ return args.left + args.buffer;
+ } else if (i === 0) {
+ return xf[i].toFixed(2);
+ } else {
+ return ((xf[i-1] + xf[i])/2).toFixed(2);
+ }
+ })
+ .attr('y', args.top)
+ .attr('width', function(d, i) {
+ //if data set is of length 1
+ if(xf.length === 1) {
+ return args.width - args.right - args.buffer;
+ } else if (i === 0) {
+ return ((xf[i+1] - xf[i]) / 2).toFixed(2);
+ } else if (i == xf.length - 1) {
+ return ((xf[i] - xf[i-1]) / 2).toFixed(2);
+ } else {
+ return ((xf[i+1] - xf[i-1]) / 2).toFixed(2);
+ }
+ })
+ .attr('height', args.height - args.bottom - args.top - args.buffer)
+ .attr('opacity', 0)
+ .on('mouseover', this.rolloverOn(args))
+ .on('mouseout', this.rolloverOff(args))
+ .on('mousemove', this.rolloverMove(args));
+ }
+
+ //for single line, use rects
+ else {
+ //set to 1 unless we have a custom increment series
+ line_id = 1;
+ if (args.custom_line_color_map.length > 0) {
+ line_id = args.custom_line_color_map[0];
+ }
+
+ g = svg.append('g')
+ .attr('class', 'mg-rollover-rect');
+
+ xf = args.data[0].map(args.scalefns.xf);
+
+ g.selectAll('.mg-rollover-rects')
+ .data(args.data[0]).enter()
+ .append('rect')
+ .attr('class', function(d, i) {
+ if (args.linked) {
+ var v = d[args.x_accessor];
+ var formatter = d3.time.format(args.linked_format);
+
+ //only format when x-axis is date
+ var id = (typeof v === 'number')
+ ? i
+ : formatter(v);
+
+ return 'mg-line' + line_id + '-color ' + 'roll_' + id;
+ } else {
+ return 'mg-line' + line_id + '-color';
+ }
+ })
+ .attr('x', function(d, i) {
+ //if data set is of length 1
+ if (xf.length === 1) {
+ return args.left + args.buffer;
+ } else if (i === 0) {
+ return xf[i].toFixed(2);
+ } else {
+ return ((xf[i-1] + xf[i])/2).toFixed(2);
+ }
+ })
+ .attr('y', function(d, i) {
+ return (args.data.length > 1)
+ ? args.scalefns.yf(d) - 6 //multi-line chart sensitivity
+ : args.top;
+ })
+ .attr('width', function(d, i) {
+ //if data set is of length 1
+ if (xf.length === 1) {
+ return args.width - args.right - args.buffer;
+ } else if (i === 0) {
+ return ((xf[i+1] - xf[i]) / 2).toFixed(2);
+ } else if (i === xf.length - 1) {
+ return ((xf[i] - xf[i-1]) / 2).toFixed(2);
+ } else {
+ return ((xf[i+1] - xf[i-1]) / 2).toFixed(2);
+ }
+ })
+ .attr('height', function(d, i) {
+ return (args.data.length > 1)
+ ? 12 //multi-line chart sensitivity
+ : args.height - args.bottom - args.top - args.buffer;
+ })
+ .attr('opacity', 0)
+ .on('mouseover', this.rolloverOn(args))
+ .on('mouseout', this.rolloverOff(args))
+ .on('mousemove', this.rolloverMove(args));
+ }
+
+ //if the dataset is of length 1, trigger the rollover for our solitary rollover rect
+ if (args.data.length == 1 && args.data[0].length == 1) {
+ svg.select('.mg-rollover-rect rect')
+ .on('mouseover')(args.data[0][0], 0);
+ } else if (args.data.length > 1) {
+ //otherwise, trigger it for an appropriate line in a multi-line chart
+ //@todo this will only trigger one of the lines, even if there are more than one
+ for (var i = 0; i < args.data.length; i++) {
+ if (args.data[i].length == 1) {
+ svg.selectAll('.mg-voronoi .mg-line' + (i + 1) + '-color')
+ .on('mouseover')(args.data[i][0], 0);
+ }
+ }
+ }
+
+ return this;
+ };
+
+ this.rolloverOn = function(args) {
+ var svg = mg_get_svg_child_of(args.target);
+ var fmt;
+ switch(args.processed.x_time_frame) {
+ case 'seconds':
+ fmt = d3.time.format('%b %e, %Y %H:%M:%S');
+ break;
+ case 'less-than-a-day':
+ fmt = d3.time.format('%b %e, %Y %I:%M%p');
+ break;
+ case 'four-days':
+ fmt = d3.time.format('%b %e, %Y %I:%M%p');
+ break;
+ default:
+ fmt = d3.time.format('%b %e, %Y');
+ }
+
+ return function(d, i) {
+
+ if (args.aggregate_rollover && args.data.length > 1) {
+
+ // hide the circles in case a non-contiguous series is present
+ svg.selectAll('circle.mg-line-rollover-circle')
+ .style('opacity', 0);
+
+ d.values.forEach(function(datum) {
+
+ if (datum[args.x_accessor] >= args.processed.min_x &&
+ datum[args.x_accessor] <= args.processed.max_x &&
+ datum[args.y_accessor] >= args.processed.min_y &&
+ datum[args.y_accessor] <= args.processed.max_y
+ ){
+ var circle = svg.select('circle.mg-line' + datum.line_id + '-color')
+ .attr({
+ 'cx': function() {
+ return args.scales.X(datum[args.x_accessor]).toFixed(2);
+ },
+ 'cy': function() {
+ return args.scales.Y(datum[args.y_accessor]).toFixed(2);
+ },
+ 'r': args.point_size
+ })
+ .style('opacity', 1);
+ }
+ });
+ } else if (args.missing_is_hidden
+ && d[args.y_accessor] == 0
+ && d['missing']
+ ) {
+ //disable rollovers for hidden parts of the line
+ return;
+ } else {
+
+ //show circle on mouse-overed rect
+ if (d[args.x_accessor] >= args.processed.min_x &&
+ d[args.x_accessor] <= args.processed.max_x &&
+ d[args.y_accessor] >= args.processed.min_y &&
+ d[args.y_accessor] <= args.processed.max_y
+ ){
+ svg.selectAll('circle.mg-line-rollover-circle')
+ .attr('class', "")
+ .attr('class', 'mg-area' + d.line_id + '-color')
+ .classed('mg-line-rollover-circle', true)
+ .attr('cx', function() {
+ return args.scales.X(d[args.x_accessor]).toFixed(2);
+ })
+ .attr('cy', function() {
+ return args.scales.Y(d[args.y_accessor]).toFixed(2);
+ })
+ .attr('r', args.point_size)
+ .style('opacity', 1);
+ }
+
+ //trigger mouseover on all rects for this date in .linked charts
+ if (args.linked && !MG.globals.link) {
+ MG.globals.link = true;
+
+ var v = d[args.x_accessor];
+ var formatter = d3.time.format(args.linked_format);
+
+ //only format when y-axis is date
+ var id = (typeof v === 'number')
+ ? i
+ : formatter(v);
+
+ //trigger mouseover on matching line in .linked charts
+ d3.selectAll('.mg-line' + d.line_id + '-color.roll_' + id)
+ .each(function(d, i) {
+ d3.select(this).on('mouseover')(d,i);
+ });
+ }
+ }
+
+ svg.selectAll('text')
+ .filter(function(g, j) {
+ return d === g;
+ })
+ .attr('opacity', 0.3);
+
+ var num = rolloverNumberFormatter(args);
+
+ //update rollover text
+ if (args.show_rollover_text) {
+ var textContainer = svg.select('.mg-active-datapoint'),
+ lineCount = 0,
+ lineHeight = 1.1;
+
+ textContainer.select('*').remove();
+
+ if (args.aggregate_rollover && args.data.length > 1) {
+ if (args.time_series) {
+ var date = new Date(d.key);
+
+ textContainer.append('tspan')
+ .text((fmt(date) + ' ' + args.yax_units).trim());
+
+ lineCount = 1;
+
+ d.values.forEach(function(datum) {
+ var label = textContainer.append('tspan')
+ .attr({
+ x: 0,
+ y: (lineCount * lineHeight) + 'em'
+ })
+ .text(num(datum[args.y_accessor]));
+
+ textContainer.append('tspan')
+ .attr({
+ x: -label.node().getComputedTextLength(),
+ y: (lineCount * lineHeight) + 'em'
+ })
+ .text('\u2014 ') // mdash
+ .classed('mg-hover-line' + datum.line_id + '-color', true)
+ .style('font-weight', 'bold');
+
+ lineCount++;
+ });
+
+ textContainer.append('tspan')
+ .attr('x', 0)
+ .attr('y', (lineCount * lineHeight) + 'em')
+ .text('\u00A0');
+ } else {
+ d.values.forEach(function(datum) {
+ var label = textContainer.append('tspan')
+ .attr({
+ x: 0,
+ y: (lineCount * lineHeight) + 'em'
+ })
+ .text(args.x_accessor + ': ' + datum[args.x_accessor]
+ + ', ' + args.y_accessor + ': ' + args.yax_units
+ + num(datum[args.y_accessor]));
+
+ textContainer.append('tspan')
+ .attr({
+ x: -label.node().getComputedTextLength(),
+ y: (lineCount * lineHeight) + 'em'
+ })
+ .text('\u2014 ') // mdash
+ .classed('mg-hover-line' + datum.line_id + '-color', true)
+ .style('font-weight', 'bold');
+
+ lineCount++;
+ });
+ }
+
+ // append an blank ( ) line to mdash positioning
+ textContainer.append('tspan')
+ .attr('x', 0)
+ .attr('y', (lineCount * lineHeight) + 'em')
+ .text('\u00A0');
+ } else {
+ if (args.time_series) {
+ var dd = new Date(+d[args.x_accessor]);
+ dd.setDate(dd.getDate());
+
+ textContainer.append('tspan')
+ .text(fmt(dd) + ' ' + args.yax_units
+ + num(d[args.y_accessor]));
+ }
+ else {
+ textContainer.append('tspan')
+ .text(args.x_accessor + ': ' + d[args.x_accessor]
+ + ', ' + args.y_accessor + ': ' + args.yax_units
+ + num(d[args.y_accessor]));
+ }
+ }
+ }
+
+ if (args.mouseover) {
+ args.mouseover(d, i);
+ }
+ };
+ };
+
+ this.rolloverOff = function(args) {
+ var svg = mg_get_svg_child_of(args.target);
+
+ return function(d, i) {
+ if (args.linked && MG.globals.link) {
+ MG.globals.link = false;
+
+ var v = d[args.x_accessor];
+ var formatter = d3.time.format(args.linked_format);
+
+ //only format when y-axis is date
+ var id = (typeof v === 'number')
+ ? i
+ : formatter(v);
+
+ d3.selectAll('.roll_' + id)
+ .each(function(d, i) {
+ d3.select(this).on('mouseout')(d);
+ });
+ }
+
+ //remove active datapoint text on mouse out, except if we have a single
+ svg.selectAll('circle.mg-line-rollover-circle')
+ .style('opacity', function() {
+ if (args.data.length == 1 && args.data[0].length == 1) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ });
+
+ svg.select('.mg-active-datapoint')
+ .text('');
+
+ if (args.mouseout) {
+ args.mouseout(d, i);
+ }
+ };
+ };
+
+ this.rolloverMove = function(args) {
+ return function(d, i) {
+ if (args.mousemove) {
+ args.mousemove(d, i);
+ }
+ };
+ };
+
+ this.windowListeners = function() {
+ mg_window_listeners(this.args);
+ return this;
+ };
+
+ this.init(args);
+
+ return this;
+ };
+
+ charts.histogram = function(args) {
+ 'use strict';
+ this.args = args;
+
+ this.init = function(args) {
+ raw_data_transformation(args);
+ process_histogram(args);
+ init(args);
+ x_axis(args);
+ y_axis(args);
+ return this;
+ };
+
+ this.mainPlot = function() {
+ var svg = mg_get_svg_child_of(args.target);
+
+ //remove the old histogram, add new one
+ svg.selectAll('.mg-histogram').remove();
+
+ var g = svg.append('g')
+ .attr('class', 'mg-histogram');
+
+ var bar = g.selectAll('.mg-bar')
+ .data(args.data[0])
+ .enter().append('g')
+ .attr('class', 'mg-bar')
+ .attr('transform', function(d) {
+ return "translate(" + args.scales.X(d[args.x_accessor]).toFixed(2)
+ + "," + args.scales.Y(d[args.y_accessor]).toFixed(2) + ")";
+ });
+
+ //draw bars
+ bar.append('rect')
+ .attr('x', 1)
+ .attr('width', function(d, i) {
+ if (args.data[0].length === 1) {
+ return (args.scalefns.xf(args.data[0][0])
+ - args.bar_margin).toFixed(2);
+ } else {
+ return (args.scalefns.xf(args.data[0][1])
+ - args.scalefns.xf(args.data[0][0])
+ - args.bar_margin).toFixed(2);
+ }
+ })
+ .attr('height', function(d) {
+ if (d[args.y_accessor] === 0) {
+ return 0;
+ }
+
+ return (args.height - args.bottom - args.buffer
+ - args.scales.Y(d[args.y_accessor])).toFixed(2);
+ });
+
+ return this;
+ };
+
+ this.markers = function() {
+ markers(args);
+ return this;
+ };
+
+ this.rollover = function() {
+ var svg = mg_get_svg_child_of(args.target);
+
+ //remove the old rollovers if they already exist
+ svg.selectAll('.mg-rollover-rect').remove();
+ svg.selectAll('.mg-active-datapoint').remove();
+
+ //rollover text
+ svg.append('text')
+ .attr('class', 'mg-active-datapoint')
+ .attr('xml:space', 'preserve')
+ .attr('x', args.width - args.right)
+ .attr('y', args.top / 2)
+ .attr('text-anchor', 'end');
+
+ var g = svg.append('g')
+ .attr('class', 'mg-rollover-rect');
+
+ //draw rollover bars
+ var bar = g.selectAll('.mg-bar')
+ .data(args.data[0])
+ .enter().append('g')
+ .attr('class', function(d, i) {
+ if (args.linked) {
+ return 'mg-rollover-rects roll_' + i;
+ } else {
+ return 'mg-rollover-rects';
+ }
+ })
+ .attr('transform', function(d) {
+ return "translate(" + (args.scales.X(d[args.x_accessor])) + "," + 0 + ")";
+ });
+
+ bar.append('rect')
+ .attr('x', 1)
+ .attr('y', 0)
+ .attr('width', function(d, i) {
+ //if data set is of length 1
+ if (args.data[0].length === 1) {
+ return (args.scalefns.xf(args.data[0][0])
+ - args.bar_margin).toFixed(2);
+ } else if (i !== args.data[0].length - 1) {
+ return (args.scalefns.xf(args.data[0][i + 1])
+ - args.scalefns.xf(d)).toFixed(2);
+ } else {
+ return (args.scalefns.xf(args.data[0][1])
+ - args.scalefns.xf(args.data[0][0])).toFixed(2);
+ }
+ })
+ .attr('height', function(d) {
+ return args.height;
+ })
+ .attr('opacity', 0)
+ .on('mouseover', this.rolloverOn(args))
+ .on('mouseout', this.rolloverOff(args))
+ .on('mousemove', this.rolloverMove(args));
+
+ return this;
+ };
+
+ this.rolloverOn = function(args) {
+ var svg = mg_get_svg_child_of(args.target);
+ var x_formatter = d3.time.format('%Y-%m-%d');
+
+ return function(d, i) {
+ svg.selectAll('text')
+ .filter(function(g, j) {
+ return d === g;
+ })
+ .attr('opacity', 0.3);
+
+ var fmt = d3.time.format('%b %e, %Y');
+ var num = rolloverNumberFormatter(args);
+
+ //highlight active bar
+ svg.selectAll('.mg-bar rect')
+ .filter(function(d, j) {
+ return j === i;
+ })
+ .classed('active', true);
+
+ //trigger mouseover on all matching bars
+ if (args.linked && !MG.globals.link) {
+ MG.globals.link = true;
+
+ //trigger mouseover on matching bars in .linked charts
+ d3.selectAll('.mg-rollover-rects.roll_' + i + ' rect')
+ .each(function(d) { //use existing i
+ d3.select(this).on('mouseover')(d,i);
+ });
+ }
+
+ //update rollover text
+ if (args.show_rollover_text) {
+ svg.select('.mg-active-datapoint')
+ .text(function() {
+ if (args.time_series) {
+ var dd = new Date(+d[args.x_accessor]);
+ dd.setDate(dd.getDate());
+
+ return fmt(dd) + ' ' + args.yax_units
+ + num(d[args.y_accessor]);
+ }
+ else {
+ return args.x_accessor + ': ' + num(d[args.x_accessor])
+ + ', ' + args.y_accessor + ': ' + args.yax_units
+ + num(d[args.y_accessor]);
+ }
+ });
+ }
+
+ if (args.mouseover) {
+ args.mouseover(d, i);
+ }
+ };
+ };
+
+ this.rolloverOff = function(args) {
+ var svg = mg_get_svg_child_of(args.target);
+
+ return function(d, i) {
+ if (args.linked && MG.globals.link) {
+ MG.globals.link = false;
+
+ //trigger mouseout on matching bars in .linked charts
+ d3.selectAll('.mg-rollover-rects.roll_' + i + ' rect')
+ .each(function(d) { //use existing i
+ d3.select(this).on('mouseout')(d,i);
+ });
+ }
+
+ //reset active bar
+ svg.selectAll('.mg-bar rect')
+ .classed('active', false);
+
+ //reset active data point text
+ svg.select('.mg-active-datapoint')
+ .text('');
+
+ if (args.mouseout) {
+ args.mouseout(d, i);
+ }
+ };
+ };
+
+ this.rolloverMove = function(args) {
+ return function(d, i) {
+ if (args.mousemove) {
+ args.mousemove(d, i);
+ }
+ };
+ };
+
+ this.windowListeners = function() {
+ mg_window_listeners(this.args);
+ return this;
+ };
+
+ this.init(args);
+ return this;
+ };
+
+ charts.point = function(args) {
+ 'use strict';
+ this.args = args;
+
+ this.init = function(args) {
+ raw_data_transformation(args);
+ process_point(args);
+ init(args);
+ x_axis(args);
+ y_axis(args);
+ return this;
+ };
+
+ this.markers = function() {
+ markers(args);
+ if (args.least_squares) {
+ add_ls(args);
+ }
+
+ return this;
+ };
+
+ this.mainPlot = function() {
+ var svg = mg_get_svg_child_of(args.target);
+ var g;
+
+ //remove the old points, add new one
+ svg.selectAll('.mg-points').remove();
+
+ // plot the points, pretty straight-forward
+ g = svg.append('g')
+ .classed('mg-points', true);
+
+ var pts = g.selectAll('circle')
+ .data(args.data[0])
+ .enter().append('svg:circle')
+ .attr('class', function(d, i) { return 'path-' + i; })
+ .attr('cx', args.scalefns.xf)
+ .attr('cy', args.scalefns.yf);
+
+ //are we coloring our points, or just using the default color?
+ if (args.color_accessor !== null) {
+ pts.attr('fill', args.scalefns.color);
+ pts.attr('stroke', args.scalefns.color);
+ } else {
+ pts.classed('mg-points-mono', true);
+ }
+
+ if (args.size_accessor !== null) {
+ pts.attr('r', args.scalefns.size);
+ } else {
+ pts.attr('r', args.point_size);
+ }
+
+ return this;
+ };
+
+ this.rollover = function() {
+ var svg = mg_get_svg_child_of(args.target);
+
+ //remove the old rollovers if they already exist
+ svg.selectAll('.mg-voronoi').remove();
+
+ //remove the old rollover text and circle if they already exist
+ svg.selectAll('.mg-active-datapoint').remove();
+
+ //add rollover text
+ svg.append('text')
+ .attr('class', 'mg-active-datapoint')
+ .attr('xml:space', 'preserve')
+ .attr('x', args.width - args.right)
+ .attr('y', args.top / 2)
+ .attr('text-anchor', 'end');
+
+ //add rollover paths
+ var voronoi = d3.geom.voronoi()
+ .x(args.scalefns.xf)
+ .y(args.scalefns.yf)
+ .clipExtent([[args.buffer, args.buffer], [args.width - args.buffer, args.height - args.buffer]]);
+
+ var paths = svg.append('g')
+ .attr('class', 'mg-voronoi');
+
+ paths.selectAll('path')
+ .data(voronoi(args.data[0]))
+ .enter().append('path')
+ .attr('d', function(d) {
+ if (d === undefined) {
+ return;
+ }
+
+ return 'M' + d.join(',') + 'Z';
+ })
+ .attr('class', function(d,i) {
+ return 'path-' + i;
+ })
+ .style('fill-opacity', 0)
+ .on('mouseover', this.rolloverOn(args))
+ .on('mouseout', this.rolloverOff(args))
+ .on('mousemove', this.rolloverMove(args));
+
+ return this;
+ };
+
+ this.rolloverOn = function(args) {
+ var svg = mg_get_svg_child_of(args.target);
+
+ return function(d, i) {
+ svg.selectAll('.mg-points circle')
+ .classed('selected', false);
+
+ //highlight active point
+ var pts = svg.selectAll('.mg-points circle.path-' + i)
+ .classed('selected', true);
+
+ if (args.size_accessor) {
+ pts.attr('r', function(di) {
+ return args.scalefns.size(di) + 1;
+ });
+ } else {
+ pts.attr('r', args.point_size);
+ }
+
+ //trigger mouseover on all points for this class name in .linked charts
+ if (args.linked && !globals.link) {
+ globals.link = true;
+
+ //trigger mouseover on matching point in .linked charts
+ d3.selectAll('.mg-voronoi .path-' + i)
+ .each(function() {
+ d3.select(this).on('mouseover')(d,i);
+ });
+ }
+
+ var fmt = d3.time.format('%b %e, %Y');
+ var num = rolloverNumberFormatter(args);
+
+ //update rollover text
+ if (args.show_rollover_text) {
+ svg.select('.mg-active-datapoint')
+ .text(function() {
+ if (args.time_series) {
+ var dd = new Date(+d.point[args.x_accessor]);
+ dd.setDate(dd.getDate());
+
+ return fmt(dd) + ' ' + args.yax_units + num(d.point[args.y_accessor]);
+ } else {
+ return args.x_accessor + ': ' + num(d.point[args.x_accessor])
+ + ', ' + args.y_accessor + ': ' + args.yax_units
+ + num(d.point[args.y_accessor]);
+ }
+ });
+ }
+
+ if (args.mouseover) {
+ args.mouseover(d, i);
+ }
+ };
+ };
+
+ this.rolloverOff = function(args) {
+ var svg = mg_get_svg_child_of(args.target);
+
+ return function(d,i) {
+ if (args.linked && globals.link) {
+ globals.link = false;
+
+ d3.selectAll('.mg-voronoi .path-' + i)
+ .each(function() {
+ d3.select(this).on('mouseout')(d,i);
+ });
+ }
+
+ //reset active point
+ var pts = svg.selectAll('.mg-points circle')
+ .classed('unselected', false)
+ .classed('selected', false);
+
+ if (args.size_accessor) {
+ pts.attr('r', args.scalefns.size);
+ } else {
+ pts.attr('r', args.point_size);
+ }
+
+ //reset active data point text
+ svg.select('.mg-active-datapoint')
+ .text('');
+
+ if (args.mouseout) {
+ args.mouseout(d, i);
+ }
+ };
+ };
+
+ this.rolloverMove = function(args) {
+ return function(d, i) {
+ if (args.mousemove) {
+ args.mousemove(d, i);
+ }
+ };
+ };
+
+ this.update = function(args) {
+ return this;
+ };
+
+ this.windowListeners = function() {
+ mg_window_listeners(this.args);
+ return this;
+ };
+
+ this.init(args);
+
+ return this;
+ };
+
+ // BARCHART:
+ // x - function that processes data
+ // - pass in a feature name, get a count
+ // - have raw feature: value function
+ // - need a way of changing the y axis and x axis
+ // - need to sort out rollovers
+ charts.bar = function(args) {
+ 'use strict';
+ this.args = args;
+
+ this.is_vertical = true;
+
+ this.init = function(args) {
+ raw_data_transformation(args);
+ process_categorical_variables(args);
+ init(args);
+
+ this.is_vertical = args.bar_orientation === 'vertical';
+
+ if (this.is_vertical) {
+ x_axis_categorical(args);
+ y_axis(args);
+ } else {
+ x_axis(args);
+ y_axis_categorical(args);
+ }
+ return this;
+ };
+
+ this.mainPlot = function() {
+ var svg = mg_get_svg_child_of(args.target);
+ var data = args.data[0];
+ var barplot = svg.select('g.mg-barplot');
+ var fresh_render = barplot.empty();
+
+ var bars;
+ var predictor_bars;
+ var pp, pp0;
+ var baseline_marks;
+
+ var perform_load_animation = fresh_render && args.animate_on_load;
+ var should_transition = perform_load_animation || args.transition_on_update;
+ var transition_duration = args.transition_duration || 1000;
+
+ // draw the plot on first render
+ if (barplot.empty()) {
+ barplot = svg.append('g')
+ .classed('mg-barplot', true);
+ }
+
+ bars = bars = barplot.selectAll('.mg-bar')
+ .data(data);
+
+ bars.exit().remove();
+
+ bars.enter().append('rect')
+ .classed('mg-bar', true);
+
+ if (args.predictor_accessor) {
+ predictor_bars = barplot.selectAll('.mg-bar-prediction')
+ .data(data);
+
+ predictor_bars.exit().remove();
+
+ predictor_bars.enter().append('rect')
+ .classed('mg-bar-prediction', true);
+ }
+
+ if (args.baseline_accessor) {
+ baseline_marks = barplot.selectAll('.mg-bar-baseline')
+ .data(data);
+
+ baseline_marks.exit().remove();
+
+ baseline_marks.enter().append('line')
+ .classed('mg-bar-baseline', true);
+ }
+
+ var appropriate_size;
+
+
+ // setup transitions
+ if (should_transition) {
+ bars = bars.transition()
+ .duration(transition_duration);
+
+ if (predictor_bars) {
+ predictor_bars = predictor_bars.transition()
+ .duration(transition_duration);
+ }
+
+ if (baseline_marks) {
+ baseline_marks = baseline_marks.transition()
+ .duration(transition_duration);
+ }
+ }
+
+
+ if (this.is_vertical) {
+ appropriate_size = args.scales.X.rangeBand()/1.5;
+
+ if (perform_load_animation) {
+ bars.attr({
+ height: 0,
+ y: args.scales.Y(0)
+ });
+
+ if (predictor_bars) {
+ predictor_bars.attr({
+ height: 0,
+ y: args.scales.Y(0)
+ });
+ }
+
+ if (baseline_marks) {
+ baseline_marks.attr({
+ y1: args.scales.Y(0),
+ y2: args.scales.Y(0)
+ });
+ }
+ }
+
+ bars.attr('y', args.scalefns.yf)
+ .attr('x', function(d) {
+ return args.scalefns.xf(d) + appropriate_size/2;
+ })
+ .attr('width', appropriate_size)
+ .attr('height', function(d) {
+ return 0 - (args.scalefns.yf(d) - args.scales.Y(0));
+ });
+
+
+ if (args.predictor_accessor) {
+ pp = args.predictor_proportion;
+ pp0 = pp-1;
+
+ // thick line through bar;
+ predictor_bars
+ .attr('y', function(d) {
+ return args.scales.Y(0) - (args.scales.Y(0) - args.scales.Y(d[args.predictor_accessor]));
+ })
+ .attr('x', function(d) {
+ return args.scalefns.xf(d) + pp0*appropriate_size/(pp*2) + appropriate_size/2;
+ })
+ .attr('width', appropriate_size/pp)
+ .attr('height', function(d) {
+ return 0 - (args.scales.Y(d[args.predictor_accessor]) - args.scales.Y(0));
+ });
+ }
+
+ if (args.baseline_accessor) {
+ pp = args.predictor_proportion;
+
+ baseline_marks
+ .attr('x1', function(d) {
+ return args.scalefns.xf(d)+appropriate_size/2-appropriate_size/pp + appropriate_size/2;
+ })
+ .attr('x2', function(d) {
+ return args.scalefns.xf(d)+appropriate_size/2+appropriate_size/pp + appropriate_size/2;
+ })
+ .attr('y1', function(d) { return args.scales.Y(d[args.baseline_accessor]); })
+ .attr('y2', function(d) { return args.scales.Y(d[args.baseline_accessor]); });
+ }
+ } else {
+ appropriate_size = args.scales.Y.rangeBand()/1.5;
+
+ if (perform_load_animation) {
+ bars.attr('width', 0);
+
+ if (predictor_bars) {
+ predictor_bars.attr('width', 0);
+ }
+
+ if (baseline_marks) {
+ baseline_marks.attr({
+ x1: args.scales.X(0),
+ x2: args.scales.X(0)
+ });
+ }
+ }
+
+ bars.attr('x', args.scales.X(0))
+ .attr('y', function(d) {
+ return args.scalefns.yf(d) + appropriate_size/2;
+ })
+ .attr('height', appropriate_size)
+ .attr('width', function(d) {
+ return args.scalefns.xf(d) - args.scales.X(0);
+ });
+
+
+ if (args.predictor_accessor) {
+ pp = args.predictor_proportion;
+ pp0 = pp-1;
+
+ // thick line through bar;
+ predictor_bars
+ .attr('x', args.scales.X(0))
+ .attr('y', function(d) {
+ return args.scalefns.yf(d) + pp0 * appropriate_size/(pp*2) + appropriate_size / 2;
+ })
+ .attr('height', appropriate_size / pp)
+ .attr('width', function(d) {
+ return args.scales.X(d[args.predictor_accessor]) - args.scales.X(0);
+ });
+ }
+
+ if (args.baseline_accessor) {
+ pp = args.predictor_proportion;
+
+ baseline_marks
+ .attr('x1', function(d) { return args.scales.X(d[args.baseline_accessor]); })
+ .attr('x2', function(d) { return args.scales.X(d[args.baseline_accessor]); })
+ .attr('y1', function(d) {
+ return args.scalefns.yf(d) + appropriate_size / 2 - appropriate_size / pp + appropriate_size / 2;
+ })
+ .attr('y2', function(d) {
+ return args.scalefns.yf(d) + appropriate_size / 2 + appropriate_size / pp + appropriate_size / 2;
+ });
+ }
+ }
+
+ return this;
+ };
+
+ this.markers = function() {
+ markers(args);
+ return this;
+ };
+
+ this.rollover = function() {
+ var svg = mg_get_svg_child_of(args.target);
+ var g;
+
+ //remove the old rollovers if they already exist
+ svg.selectAll('.mg-rollover-rect').remove();
+ svg.selectAll('.mg-active-datapoint').remove();
+
+ //rollover text
+ svg.append('text')
+ .attr('class', 'mg-active-datapoint')
+ .attr('xml:space', 'preserve')
+ .attr('x', args.width - args.right)
+ .attr('y', args.top / 2)
+ .attr('dy', '.35em')
+ .attr('text-anchor', 'end');
+
+ g = svg.append('g')
+ .attr('class', 'mg-rollover-rect');
+
+ //draw rollover bars
+ var bar = g.selectAll(".mg-bar-rollover")
+ .data(args.data[0]).enter()
+ .append("rect")
+ .attr('class', 'mg-bar-rollover');
+
+ if (this.is_vertical) {
+ bar.attr("x", args.scalefns.xf)
+ .attr("y", function() {
+ return args.scales.Y(0) - args.height;
+ })
+ .attr('width', args.scales.X.rangeBand())
+ .attr('height', args.height)
+ .attr('opacity', 0)
+ .on('mouseover', this.rolloverOn(args))
+ .on('mouseout', this.rolloverOff(args))
+ .on('mousemove', this.rolloverMove(args));
+ } else {
+ bar.attr("x", args.scales.X(0))
+ .attr("y", args.scalefns.yf)
+ .attr('width', args.width)
+ .attr('height', args.scales.Y.rangeBand()+2)
+ .attr('opacity', 0)
+ .on('mouseover', this.rolloverOn(args))
+ .on('mouseout', this.rolloverOff(args))
+ .on('mousemove', this.rolloverMove(args));
+ }
+ return this;
+ };
+
+ this.rolloverOn = function(args) {
+ var svg = mg_get_svg_child_of(args.target);
+ var label_accessor = this.is_vertical ? args.x_accessor : args.y_accessor;
+ var data_accessor = this.is_vertical ? args.y_accessor : args.x_accessor;
+ var label_units = this.is_vertical ? args.yax_units : args.xax_units;
+
+ return function(d, i) {
+ svg.selectAll('text')
+ .filter(function(g, j) {
+ return d === g;
+ })
+ .attr('opacity', 0.3);
+
+ var fmt = d3.time.format('%b %e, %Y');
+ var num = rolloverNumberFormatter(args);
+
+ //highlight active bar
+ svg.selectAll('g.mg-barplot .mg-bar')
+ .filter(function(d, j) {
+ return j === i;
+ })
+ .classed('active', true);
+
+ //update rollover text
+ if (args.show_rollover_text) {
+ svg.select('.mg-active-datapoint')
+ .text(function() {
+ if (args.time_series) {
+ var dd = new Date(+d[data_accessor]);
+ dd.setDate(dd.getDate());
+
+ return fmt(dd) + ' ' + label_units + num(d[label_accessor]);
+ } else {
+ return d[label_accessor] + ': ' + num(d[data_accessor]);
+ }
+ });
+ }
+
+ if (args.mouseover) {
+ args.mouseover(d, i);
+ }
+ };
+ };
+
+ this.rolloverOff = function(args) {
+ var svg = mg_get_svg_child_of(args.target);
+
+ return function(d, i) {
+ //reset active bar
+ svg.selectAll('g.mg-barplot .mg-bar')
+ .classed('active', false);
+
+ //reset active data point text
+ svg.select('.mg-active-datapoint')
+ .text('');
+
+ if (args.mouseout) {
+ args.mouseout(d, i);
+ }
+ };
+ };
+
+ this.rolloverMove = function(args) {
+ return function(d, i) {
+ if (args.mousemove) {
+ args.mousemove(d, i);
+ }
+ };
+ };
+
+ this.windowListeners = function() {
+ mg_window_listeners(this.args);
+ return this;
+ };
+
+ this.init(args);
+ return this;
+ };
+
+ /*
+ Data Tables
+
+ Along with histograms, bars, lines, and scatters, a simple data table can take you far.
+ We often just want to look at numbers, organized as a table, where columns are variables,
+ and rows are data points. Sometimes we want a cell to have a small graphic as the main
+ column element, in which case we want small multiples. sometimes we want to
+
+ var table = New data_table(data)
+ .target('div#data-table')
+ .title({accessor: 'point_name', align: 'left'})
+ .description({accessor: 'description'})
+ .number({accessor: ''})
+
+ */
+
+ MG.data_table = function(args) {
+ 'use strict';
+ this.args = args;
+ this.args.standard_col = { width: 150, font_size: 12, font_weight: 'normal' };
+ this.args.columns = [];
+ this.formatting_options = [['color', 'color'], ['font-weight', 'font_weight'], ['font-style', 'font_style'], ['font-size', 'font_size']];
+
+ this._strip_punctuation = function(s) {
+ var punctuationless = s.replace(/[^a-zA-Z0-9 _]+/g, '');
+ var finalString = punctuationless.replace(/ +?/g, "");
+ return finalString;
+ };
+
+ this._format_element = function(element, value, args) {
+ this.formatting_options.forEach(function(fo) {
+ var attr = fo[0];
+ var key = fo[1];
+ if (args[key]) element.style(attr,
+ typeof args[key] === 'string' ||
+ typeof args[key] === 'number' ?
+ args[key] : args[key](value));
+ });
+ };
+
+ this._add_column = function(_args, arg_type) {
+ var standard_column = this.args.standard_col;
+ var args = merge_with_defaults(MG.clone(_args), MG.clone(standard_column));
+ args.type = arg_type;
+ this.args.columns.push(args);
+ };
+
+ this.target = function() {
+ var target = arguments[0];
+ this.args.target = target;
+ return this;
+ };
+
+ this.title = function() {
+ this._add_column(arguments[0], 'title');
+ return this;
+ };
+
+ this.text = function() {
+ this._add_column(arguments[0], 'text');
+ return this;
+ };
+
+ this.bullet = function() {
+ /*
+ text label
+ main value
+ comparative measure
+ any number of ranges
+
+ additional args:
+ no title
+ xmin, xmax
+ format: percentage
+ xax_formatter
+ */
+ return this;
+ };
+
+ this.sparkline = function() {
+ return this;
+ };
+
+ this.number = function() {
+ this._add_column(arguments[0], 'number');
+ return this;
+ };
+
+ this.display = function() {
+ var args = this.args;
+
+ chart_title(args);
+
+ var target = args.target;
+ var table = d3.select(target).append('table').classed('mg-data-table', true);
+ var colgroup = table.append('colgroup');
+ var thead = table.append('thead');
+ var tbody = table.append('tbody');
+ var this_column;
+ var this_title;
+
+ var tr, th, td_accessor, td_type, td_value, th_text, td_text, td;
+ var col;
+ var h;
+
+ tr = thead.append('tr');
+
+ for (h = 0; h < args.columns.length; h++) {
+ var this_col = args.columns[h];
+ td_type = this_col.type;
+ th_text = this_col.label;
+ th_text = th_text === undefined ? '' : th_text;
+ th = tr.append('th')
+ .style('width', this_col.width)
+ .style('text-align', td_type === 'title' ? 'left' : 'right')
+ .text(th_text);
+
+ if (args.show_tooltips && this_col.description) {
+ th.append('i')
+ .classed('fa', true)
+ .classed('fa-question-circle', true)
+ .classed('fa-inverse', true);
+
+ $(th[0]).popover({
+ html: true,
+ animation: false,
+ content: this_col.description,
+ trigger: 'hover',
+ placement: 'top',
+ container: $(th[0])
+ });
+ }
+ }
+
+ for (h = 0; h < args.columns.length; h++) {
+ col = colgroup.append('col');
+ if (args.columns[h].type === 'number') {
+ col.attr('align', 'char').attr('char', '.');
+ }
+ }
+
+ for (var i=0; i < args.data.length; i++) {
+ tr = tbody.append('tr');
+ for (var j = 0; j < args.columns.length; j++) {
+ this_column = args.columns[j];
+ td_accessor = this_column.accessor;
+ td_value = td_text = args.data[i][td_accessor];
+ td_type = this_column.type;
+
+ if (td_type === 'number') {
+ //td_text may need to be rounded
+ if (this_column.hasOwnProperty('round') && !this_column.hasOwnProperty('format')) {
+ // round according to the number value in this_column.round
+ td_text = d3.format('0,.'+this_column.round+'f')(td_text);
+ }
+
+ if (this_column.hasOwnProperty('value_formatter')) {
+ // provide a function that formats the text according to the function this_column.format.
+ td_text = this_column.value_formatter(td_text);
+ }
+
+ if (this_column.hasOwnProperty('format')) {
+ // this is a shorthand for percentage formatting, and others if need be.
+ // supported: 'percentage', 'count', 'temperature'
+
+ if (this_column.round) {
+ td_text = d3.round(td_text, this_column.round);
+ }
+
+ var this_format = this_column.format;
+ var formatter;
+
+ if (this_format === 'percentage') formatter = d3.format('%p');
+ if (this_format === 'count') formatter = d3.format("0,000");
+ if (this_format === 'temperature') formatter = function(t) { return t +'°'; };
+
+ td_text = formatter(td_text);
+ }
+
+ if (this_column.hasOwnProperty('currency')) {
+ // this is another shorthand for formatting according to a currency amount, which gets appended to front of number
+ td_text = this_column.currency + td_text;
+ }
+ }
+
+ td = tr.append('td')
+ .classed('table-' + td_type, true)
+ .classed('table-' + td_type + '-' + this._strip_punctuation(td_accessor), true)
+ .attr('data-value', td_value)
+ .style('width', this_column.width)
+ .style('text-align', td_type === 'title' || td_type === 'text' ? 'left' : 'right');
+
+ this._format_element(td, td_value, this_column);
+
+ if (td_type === 'title') {
+ this_title = td.append('div').text(td_text);
+ this._format_element(this_title, td_text, this_column);
+
+ if (args.columns[j].hasOwnProperty('secondary_accessor')) {
+ td.append('div')
+ .text(args.data[i][args.columns[j].secondary_accessor])
+ .classed("secondary-title", true);
+ }
+ } else {
+ td.text(td_text);
+ }
+ }
+ }
+
+ return this;
+ };
+
+ return this;
+ };
+
+ charts.missing = function(args) {
+ 'use strict';
+ this.args = args;
+
+ this.init = function(args) {
+ chart_title(args);
+
+ // create svg if one doesn't exist
+ d3.select(args.target).selectAll('svg').data([args])
+ .enter().append('svg')
+ .attr('width', args.width)
+ .attr('height', args.height);
+
+ var svg = mg_get_svg_child_of(args.target);
+
+ // has the width or height changed?
+ if (args.width !== Number(svg.attr('width'))) {
+ svg.attr('width', args.width);
+ }
+
+ if (args.height !== Number(svg.attr('height'))) {
+ svg.attr('height', args.height);
+ }
+
+ // delete child elements
+ d3.select(args.target).selectAll('svg *').remove();
+
+ // add missing class
+ svg.classed('mg-missing', true);
+
+ // do we need to clear the legend?
+ if (args.legend_target) {
+ d3.select(args.legend_target).html('');
+ }
+
+ //are we adding a background placeholder
+ if (args.show_missing_background) {
+ var data = [];
+ for (var x = 1; x <= 50; x++) {
+ data.push({'x': x, 'y': Math.random() - (x * 0.03)});
+ }
+
+ args.scales.X = d3.scale.linear()
+ .domain([0, data.length])
+ .range([args.left + args.buffer, args.width - args.right - args.buffer]);
+
+ args.scales.Y = d3.scale.linear()
+ .domain([-2, 2])
+ .range([args.height - args.bottom - args.buffer*2, args.top]);
+
+ args.scalefns.xf = function(di) { return args.scales.X(di.x); };
+ args.scalefns.yf = function(di) { return args.scales.Y(di.y); };
+
+ var line = d3.svg.line()
+ .x(args.scalefns.xf)
+ .y(args.scalefns.yf)
+ .interpolate(args.interpolate);
+
+ var area = d3.svg.area()
+ .x(args.scalefns.xf)
+ .y0(args.scales.Y.range()[0])
+ .y1(args.scalefns.yf)
+ .interpolate(args.interpolate);
+
+ var g = svg.append('g')
+ .attr('class', 'mg-missing-pane');
+
+ g.append('svg:rect')
+ .classed('mg-missing-background', true)
+ .attr('x', args.buffer)
+ .attr('y', args.buffer)
+ .attr('width', args.width-args.buffer*2)
+ .attr('height', args.height-args.buffer*2)
+ .attr('rx',15)
+ .attr('ry', 15);
+
+ g.append('path')
+ .attr('class', 'mg-main-line mg-line1-color')
+ .attr('d', line(data));
+
+ g.append('path')
+ .attr('class', 'mg-main-area mg-area1-color')
+ .attr('d', area(data));
+ }
+
+ // add missing text
+ svg.selectAll('.mg-missing-text').data([args.missing_text])
+ .enter().append('text')
+ .attr('class', 'mg-missing-text')
+ .attr('x', args.width / 2)
+ .attr('y', args.height / 2)
+ .attr('dy', '.50em')
+ .attr('text-anchor', 'middle')
+ .text(args.missing_text);
+
+ return this;
+ };
+
+ this.init(args);
+ return this;
+ };
+
+ function raw_data_transformation(args) {
+ 'use strict';
+
+ // We need to account for a few data format cases:
+ // 1. [{key:__, value:__}, ...] // unnested obj-arrays
+ // 2. [[{key:__, value:__}, ...], [{key:__, value:__}, ...]] // nested obj-arrays
+ // 3. [[4323, 2343],..] // unnested 2d array
+ // 4. [[[4323, 2343],..] , [[4323, 2343],..]] // nested 2d array
+ if (args.chart_type === 'line') {
+ var is_unnested_obj_array = (args.data[0] instanceof Object && !(args.data[0] instanceof Array));
+ var is_unnested_array_of_arrays = (
+ args.data[0] instanceof Array &&
+ !(args.data[0][0] instanceof Object &&
+ !(args.data[0][0] instanceof Date)));
+
+ if (is_unnested_obj_array || is_unnested_array_of_arrays) {
+ args.data = [args.data];
+ }
+ } else {
+ if (!(args.data[0] instanceof Array)) {
+ args.data = [args.data];
+ }
+ }
+
+ if (args.y_accessor instanceof Array) {
+ args.data = args.data.map(function(_d) {
+ return args.y_accessor.map(function(ya) {
+ return _d.map(function(di) {
+ di = MG.clone(di);
+ if (di[ya] === undefined) {
+ return undefined;
+ }
+ di['multiline_y_accessor'] = di[ya];
+ return di;
+ }).filter(function(di) {
+ return di !== undefined;
+ });
+ });
+ })[0];
+
+ args.y_accessor = 'multiline_y_accessor';
+ }
+
+ //sort x-axis data
+ if (args.chart_type === 'line') {
+ for (var i = 0; i < args.data.length; i++) {
+ args.data[i].sort(function(a, b) {
+ return a[args.x_accessor] - b[args.x_accessor];
+ });
+ }
+ }
+
+ return this;
+ }
+
+ function process_line(args) {
+ 'use strict';
+ //do we have a time-series?
+ var is_time_series = args.data[0][0][args.x_accessor] instanceof Date
+ ? true
+ : false;
+
+ //force linear interpolation when missing_is_hidden is enabled
+ if (args.missing_is_hidden) {
+ args.interpolate = 'linear';
+ }
+
+ //are we replacing missing y values with zeros?
+ if ((args.missing_is_zero || args.missing_is_hidden)
+ && args.chart_type === 'line'
+ && is_time_series
+ ) {
+ for (var i = 0; i < args.data.length; i++) {
+ //we need to have a dataset of length > 2, so if it's less than that, skip
+ if (args.data[i].length == 1) {
+ continue;
+ }
+
+ var first = args.data[i][0];
+ var last = args.data[i][args.data[i].length-1];
+ //initialize our new array for storing the processed data
+ var processed_data = [];
+
+ //we'll be starting from the day after our first date
+ var start_date = MG.clone(first[args.x_accessor]).setDate(first[args.x_accessor].getDate() + 1);
+
+ //if we've set a max_x, add data points up to there
+ var from = (args.min_x) ? args.min_x : start_date;
+ var upto = (args.max_x) ? args.max_x : last[args.x_accessor];
+ for (var d = new Date(from); d <= upto; d.setDate(d.getDate() + 1)) {
+ var o = {};
+ d.setHours(0, 0, 0, 0);
+
+ //add the first date item (judge me not, world)
+ //we'll be starting from the day after our first date
+ if (Date.parse(d) === Date.parse(new Date(start_date))) {
+ processed_data.push(MG.clone(args.data[i][0]));
+ }
+
+ //check to see if we already have this date in our data object
+ var existing_o = null;
+ args.data[i].forEach(function(val, i) {
+ if (Date.parse(val[args.x_accessor]) === Date.parse(new Date(d))) {
+ existing_o = val;
+
+ return false;
+ }
+ });
+
+ //if we don't have this date in our data object, add it and set it to zero
+ if (!existing_o) {
+ o[args.x_accessor] = new Date(d);
+ o[args.y_accessor] = 0;
+ o['missing'] = true; //we want to distinguish between zero-value and missing observations
+ processed_data.push(o);
+ }
+ //otherwise, use the existing object for that date
+ else {
+ processed_data.push(existing_o);
+ }
+
+ //add the last data item
+ if (Date.parse(d) === Date.parse(new Date(last[args.x_accessor]))) {
+ processed_data.push(last);
+ }
+ }
+
+ //update our date object
+ args.data[i] = processed_data;
+ }
+ }
+
+ return this;
+ }
+
+ function process_histogram(args) {
+ 'use strict';
+ // if args.binned=False, then we need to bin the data appropriately.
+ // if args.binned=True, then we need to make sure to compute the relevant computed data.
+ // the outcome of either of these should be something in args.computed_data.
+ // the histogram plotting function will be looking there for the data to plot.
+
+ // we need to compute an array of objects.
+ // each object has an x, y, and dx.
+
+ // histogram data is always single dimension
+ var our_data = args.data[0];
+ var extracted_data;
+ if (args.binned === false) {
+ // use d3's built-in layout.histogram functionality to compute what you need.
+
+ if (typeof(our_data[0]) === 'object') {
+ // we are dealing with an array of objects. Extract the data value of interest.
+ extracted_data = our_data
+ .map(function(d) {
+ return d[args.x_accessor];
+ });
+ } else if (typeof(our_data[0]) === 'number') {
+ // we are dealing with a simple array of numbers. No extraction needed.
+ extracted_data = our_data;
+ } else {
+ console.log('TypeError: expected an array of numbers, found ' + typeof(our_data[0]));
+ return;
+ }
+
+ var hist = d3.layout.histogram();
+ if (args.bins) {
+ hist = hist.bins(args.bins);
+ }
+
+ args.processed_data = hist(extracted_data)
+ .map(function(d) {
+ // extract only the data we need per data point.
+ return {'x': d.x, 'y': d.y, 'dx': d.dx};
+ });
+ } else {
+ // here, we just need to reconstruct the array of objects
+ // take the x accessor and y accessor.
+ // pull the data as x and y. y is count.
+
+ args.processed_data = our_data.map(function(d) {
+ return {'x': d[args.x_accessor], 'y': d[args.y_accessor]};
+ });
+
+ var this_pt;
+ var next_pt;
+
+ // we still need to compute the dx component for each data point
+ for (var i=0; i < args.processed_data.length; i++) {
+ this_pt = args.processed_data[i];
+ if (i === args.processed_data.length - 1) {
+ this_pt.dx = args.processed_data[i-1].dx;
+ } else {
+ next_pt = args.processed_data[i+1];
+ this_pt.dx = next_pt.x - this_pt.x;
+ }
+ }
+ }
+
+ args.data = [args.processed_data];
+ args.x_accessor = args.processed_x_accessor;
+ args.y_accessor = args.processed_y_accessor;
+
+ return this;
+ }
+
+ function process_categorical_variables(args) {
+ // For use with bar charts, etc.
+ 'use strict';
+ var extracted_data, processed_data={}, pd=[];
+ var our_data = args.data[0];
+ var label_accessor = args.bar_orientation === 'vertical' ? args.x_accessor : args.y_accessor;
+ var data_accessor = args.bar_orientation === 'vertical' ? args.y_accessor : args.x_accessor;
+
+ args.categorical_variables = [];
+
+ if (args.binned === false) {
+ if (typeof(our_data[0]) === 'object') {
+ // we are dealing with an array of objects. Extract the data value of interest.
+ extracted_data = our_data
+ .map(function(d) {
+ return d[label_accessor];
+ });
+ } else {
+ extracted_data = our_data;
+ }
+
+ var this_dp;
+
+ for (var i=0; i< extracted_data.length; i++) {
+ this_dp=extracted_data[i];
+ if (args.categorical_variables.indexOf(this_dp) === -1) args.categorical_variables.push(this_dp);
+ if (!processed_data.hasOwnProperty(this_dp)) processed_data[this_dp] = 0;
+
+ processed_data[this_dp] += 1;
+ }
+
+ processed_data = Object.keys(processed_data).map(function(d) {
+ var obj = {};
+ obj[data_accessor] = processed_data[d];
+ obj[label_accessor] = d;
+ return obj;
+ });
+ } else {
+ // nothing needs to really happen here.
+ processed_data = our_data;
+ args.categorical_variables = d3.set(processed_data.map(function(d) {
+ return d[label_accessor];
+ })).values();
+ args.categorical_variables.reverse();
+ }
+
+ args.data = [processed_data];
+ return this;
+ }
+
+ function process_point(args) {
+ 'use strict';
+ var data = args.data[0];
+ var x = data.map(function(d) { return d[args.x_accessor]; });
+ var y = data.map(function(d) { return d[args.y_accessor]; });
+
+ if (args.least_squares) {
+ args.ls_line = least_squares(x,y);
+ }
+
+ //args.lowess_line = lowess_robust(x,y, .5, 100)
+ return this;
+
+ }
+
+ function add_ls(args) {
+ var svg = mg_get_svg_child_of(args.target);
+ var data = args.data[0];
+ var min_x = args.scales.X.ticks(args.xax_count)[0];
+ var max_x = args.scales.X.ticks(args.xax_count)[args.scales.X.ticks(args.xax_count).length - 1];
+
+ d3.select(args.target).selectAll('.mg-least-squares-line').remove();
+
+ svg.append('svg:line')
+ .attr('x1', args.scales.X(min_x))
+ .attr('x2', args.scales.X(max_x))
+ .attr('y1', args.scales.Y(args.ls_line.fit(min_x)) )
+ .attr('y2', args.scales.Y(args.ls_line.fit(max_x)) )
+ .attr('class', 'mg-least-squares-line');
+ }
+
+ function add_lowess(args) {
+ var svg = d3.select($(args.target).find('svg').get(0));
+ var lowess = args.lowess_line;
+
+ var line = d3.svg.line()
+ .x(function(d) { return args.scales.X(d.x); })
+ .y(function(d) { return args.scales.Y(d.y); })
+ .interpolate(args.interpolate);
+
+ svg.append('path')
+ .attr('d', line(lowess))
+ .attr('class', 'mg-lowess-line');
+ }
+
+ function lowess_robust(x, y, alpha, inc) {
+ // Used http://www.unc.edu/courses/2007spring/biol/145/001/docs/lectures/Oct27.html
+ // for the clear explanation of robust lowess.
+
+ // calculate the the first pass.
+ var _l;
+ var r = [];
+ var yhat = d3.mean(y);
+ var i;
+ for (i = 0; i < x.length; i += 1) { r.push(1); }
+ _l = _calculate_lowess_fit(x,y,alpha, inc, r);
+ var x_proto = _l.x;
+ var y_proto = _l.y;
+
+ // Now, take the fit, recalculate the weights, and re-run LOWESS using r*w instead of w.
+
+ for (i = 0; i < 100; i += 1) {
+ r = d3.zip(y_proto, y).map(function(yi) {
+ return Math.abs(yi[1] - yi[0]);
+ });
+
+ var q = d3.quantile(r.sort(), 0.5);
+
+ r = r.map(function(ri) {
+ return _bisquare_weight(ri / (6 * q));
+ });
+
+ _l = _calculate_lowess_fit(x,y,alpha,inc, r);
+ x_proto = _l.x;
+ y_proto = _l.y;
+ }
+
+ return d3.zip(x_proto, y_proto).map(function(d) {
+ var p = {};
+ p.x = d[0];
+ p.y = d[1];
+ return p;
+ });
+ }
+
+ function lowess(x, y, alpha, inc) {
+ var r = [];
+ for (var i = 0; i < x.length; i += 1) { r.push(1); }
+ var _l = _calculate_lowess_fit(x, y, alpha, inc, r);
+ }
+
+ function least_squares(x_, y_) {
+ var x, y, xi, yi,
+ _x = 0,
+ _y = 0,
+ _xy = 0,
+ _xx = 0;
+
+ var n = x_.length;
+ if (x_[0] instanceof Date) {
+ x = x_.map(function(d) {
+ return d.getTime();
+ });
+ } else {
+ x = x_;
+ }
+
+ if (y_[0] instanceof Date) {
+ y = y_.map(function(d) {
+ return d.getTime();
+ });
+ } else {
+ y = y_;
+ }
+
+ var xhat = d3.mean(x);
+ var yhat = d3.mean(y);
+ var numerator = 0, denominator = 0;
+
+ for (var i = 0; i < x.length; i++) {
+ xi = x[i];
+ yi = y[i];
+ numerator += (xi - xhat) * (yi - yhat);
+ denominator += (xi - xhat) * (xi - xhat);
+ }
+
+ var beta = numerator / denominator;
+ var x0 = yhat - beta * xhat;
+
+ return {
+ x0: x0,
+ beta: beta,
+ fit: function(x) {
+ return x0 + x * beta;
+ }
+ };
+ }
+
+ function _pow_weight(u, w) {
+ if (u >= 0 && u <= 1) {
+ return Math.pow(1 - Math.pow(u,w), w);
+ } else {
+ return 0;
+ }
+ }
+
+ function _bisquare_weight(u) {
+ return _pow_weight(u, 2);
+ }
+
+ function _tricube_weight(u) {
+ return _pow_weight(u, 3);
+ }
+
+ function _neighborhood_width(x0, xis) {
+ return Array.max(xis.map(function(xi) {
+ return Math.abs(x0 - xi);
+ }));
+ }
+
+ function _manhattan(x1,x2) {
+ return Math.abs(x1 - x2);
+ }
+
+ function _weighted_means(wxy) {
+ var wsum = d3.sum(wxy.map(function(wxyi) { return wxyi.w; }));
+
+ return {
+ xbar: d3.sum(wxy.map(function(wxyi) {
+ return wxyi.w * wxyi.x;
+ })) / wsum,
+ ybar:d3.sum(wxy.map(function(wxyi) {
+ return wxyi.w * wxyi.y;
+ })) / wsum
+ };
+ }
+
+ function _weighted_beta(wxy, xbar, ybar) {
+ var num = d3.sum(wxy.map(function(wxyi) {
+ return Math.pow(wxyi.w, 2) * (wxyi.x - xbar) * (wxyi.y - ybar);
+ }));
+
+ var denom = d3.sum(wxy.map(function(wxyi) {
+ return Math.pow(wxyi.w, 2) * (Math.pow(wxyi.x - xbar), 2);
+ }));
+
+ return num / denom;
+ }
+
+ function _weighted_least_squares(wxy) {
+ var ybar, xbar, beta_i, x0;
+
+ var _wm = _weighted_means(wxy);
+
+ xbar = _wm.xbar;
+ ybar = _wm.ybar;
+
+ var beta = _weighted_beta(wxy, xbar, ybar);
+
+ return {
+ beta : beta,
+ xbar : xbar,
+ ybar : ybar,
+ x0 : ybar - beta * xbar
+
+ };
+ }
+
+ function _calculate_lowess_fit(x, y, alpha, inc, residuals) {
+ // alpha - smoothing factor. 0 < alpha < 1/
+ //
+ //
+ var k = Math.floor(x.length * alpha);
+
+ var sorted_x = x.slice();
+
+ sorted_x.sort(function(a,b) {
+ if (a < b) { return -1; }
+ else if (a > b) { return 1; }
+
+ return 0;
+ });
+
+ var x_max = d3.quantile(sorted_x, 0.98);
+ var x_min = d3.quantile(sorted_x, 0.02);
+
+ var xy = d3.zip(x, y, residuals).sort();
+
+ var size = Math.abs(x_max - x_min) / inc;
+
+ var smallest = x_min;
+ var largest = x_max;
+ var x_proto = d3.range(smallest, largest, size);
+
+ var xi_neighbors;
+ var x_i, beta_i, x0_i, delta_i, xbar, ybar;
+
+ // for each prototype, find its fit.
+ var y_proto = [];
+
+ for (var i = 0; i < x_proto.length; i += 1) {
+ x_i = x_proto[i];
+
+ // get k closest neighbors.
+ xi_neighbors = xy.map(function(xyi) {
+ return [
+ Math.abs(xyi[0] - x_i),
+ xyi[0],
+ xyi[1],
+ xyi[2]];
+ }).sort().slice(0, k);
+
+ // Get the largest distance in the neighbor set.
+ delta_i = d3.max(xi_neighbors)[0];
+
+ // Prepare the weights for mean calculation and WLS.
+
+ xi_neighbors = xi_neighbors.map(function(wxy) {
+ return {
+ w : _tricube_weight(wxy[0] / delta_i) * wxy[3],
+ x : wxy[1],
+ y :wxy[2]
+ };
+ });
+
+ // Find the weighted least squares, obviously.
+ var _output = _weighted_least_squares(xi_neighbors);
+
+ x0_i = _output.x0;
+ beta_i = _output.beta;
+
+ //
+ y_proto.push(x0_i + beta_i * x_i);
+ }
+
+ return {x: x_proto, y: y_proto};
+ }
+
+ function rolloverNumberFormatter(args) {
+ var num;
+ if (args.format === 'count') {
+ num = function(d_) {
+ var is_float = d_ % 1 !== 0;
+ var n = d3.format("0,000");
+ d_ = is_float ? d3.round(d_, args.decimals) : d_;
+ return n(d_);
+ };
+ } else {
+ num = function(d_) {
+ var fmt_string = (args.decimals ? '.' + args.decimals : '' ) + '%';
+ var n = d3.format(fmt_string);
+ return n(d_);
+ };
+ }
+ return num;
+ }
+
+ // http://bl.ocks.org/mbostock/3916621
+ function pathTween(d1, precision) {
+ return function() {
+ var path0 = this,
+ path1 = path0.cloneNode(),
+ n0 = path0.getTotalLength(),
+ n1 = (path1.setAttribute("d", d1), path1).getTotalLength();
+
+ // Uniform sampling of distance based on specified precision.
+ var distances = [0], i = 0, dt = precision / Math.max(n0, n1);
+ while ((i += dt) < 1) distances.push(i);
+ distances.push(1);
+
+ // Compute point-interpolators at each distance.
+ var points = distances.map(function(t) {
+ var p0 = path0.getPointAtLength(t * n0),
+ p1 = path1.getPointAtLength(t * n1);
+ return d3.interpolate([p0.x, p0.y], [p1.x, p1.y]);
+ });
+
+ return function(t) {
+ return t < 1 ? "M" + points.map(function(p) { return p(t); }).join("L") : d1;
+ };
+ };
+ }
+
+ //a set of helper functions, some that we've written, others that we've borrowed
+
+ MG.convert = {};
+
+ MG.convert.date = function(data, accessor, time_format) {
+ time_format = (typeof time_format === "undefined") ? '%Y-%m-%d' : time_format;
+ data = data.map(function(d) {
+ var fff = d3.time.format(time_format);
+ d[accessor] = fff.parse(d[accessor]);
+ return d;
+ });
+
+ return data;
+ };
+
+ MG.convert.number = function(data, accessor) {
+ data = data.map(function(d) {
+ d[accessor] = Number(d[accessor]);
+ return d;
+ });
+
+ return data;
+ };
+
+ function mg_get_svg_child_of(selector_or_node) {
+ return d3.select(selector_or_node).select('svg');
+ }
+
+ function mg_strip_punctuation(s) {
+ var processed_s;
+
+ if (typeof(s) == 'string') {
+ processed_s = s;
+ } else {
+ // args.target is
+ if (s.id != '') {
+ processed_s = s.id;
+ } else if (args.target.className != '') {
+ processed_s = s.className;
+ } else if (args.target.nodeName !='') {
+ processed_s = s.nodeName;
+ } else {
+ console.warn('The specified target element ' + s + ' has no unique attributes.');
+ }
+ }
+
+ var punctuationless = processed_s.replace(/[^a-zA-Z0-9 _]+/g, '');
+ var finalString = punctuationless.replace(/ +?/g, "");
+
+ return finalString;
+ }
+
+ function get_pixel_dimension(target, dimension) {
+ return Number(d3.select(target).style(dimension).replace(/px/g, ''));
+ }
+
+ function get_width(target) {
+ return get_pixel_dimension(target, 'width');
+ }
+
+ function get_height(target) {
+ return get_pixel_dimension(target, 'height');
+ }
+
+ function isNumeric(n) {
+ return !isNaN(parseFloat(n)) && isFinite(n);
+ }
+
+ var each = function(obj, iterator, context) {
+ // yanked out of underscore
+ if (obj === null) return obj;
+ if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
+ obj.forEach(iterator, context);
+ } else if (obj.length === +obj.length) {
+ for (var i = 0, length = obj.length; i < length; i++) {
+ if (iterator.call(context, obj[i], i, obj) === breaker) return;
+ }
+ } else {
+ for (var k in obj) {
+ if (iterator.call(context, obj[k], k, obj) === breaker) return;
+ }
+ }
+
+ return obj;
+ };
+
+ function merge_with_defaults(obj) {
+ // taken from underscore
+ each(Array.prototype.slice.call(arguments, 1), function(source) {
+ if (source) {
+ for (var prop in source) {
+ if (obj[prop] === void 0) obj[prop] = source[prop];
+ }
+ }
+ });
+
+ return obj;
+ }
+
+ function number_of_values(data, accessor, value) {
+ var values = data.filter(function(d) {
+ return d[accessor] === value;
+ });
+
+ return values.length;
+ }
+
+ function has_values_below(data, accessor, value) {
+ var values = data.filter(function(d) {
+ return d[accessor] <= value;
+ });
+
+ return values.length > 0;
+ }
+
+ function has_too_many_zeros(data, accessor, zero_count) {
+ return number_of_values(data, accessor, 0) >= zero_count;
+ }
+
+ //deep copy
+ //http://stackoverflow.com/questions/728360/most-elegant-way-to-clone-a-javascript-object
+ MG.clone = function(obj) {
+ var copy;
+
+ // Handle the 3 simple types, and null or undefined
+ if (null === obj || "object" !== 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] = MG.clone(obj[i]);
+ }
+ return copy;
+ }
+
+ // Handle Object
+ if (obj instanceof Object) {
+ copy = {};
+ for (var attr in obj) {
+ if (obj.hasOwnProperty(attr)) copy[attr] = MG.clone(obj[attr]);
+ }
+ return copy;
+ }
+
+ throw new Error("Unable to copy obj! Its type isn't supported.");
+ };
+
+ //give us the difference of two int arrays
+ //http://radu.cotescu.com/javascript-diff-function/
+ function arrDiff(a,b) {
+ var seen = [],
+ diff = [],
+ i;
+ for (i = 0; i < b.length; i++)
+ seen[b[i]] = true;
+ for (i = 0; i < a.length; i++)
+ if (!seen[a[i]])
+ diff.push(a[i]);
+ return diff;
+ }
+
+
+ /**
+ Print warning message to the console when a feature has been scheduled for removal
+
+ @author Dan de Havilland (github.com/dandehavilland)
+ @date 2014-12
+ */
+ function warnDeprecation(message, untilVersion) {
+ console.warn('Deprecation: ' + message + (untilVersion ? '. This feature will be removed in ' + untilVersion + '.' : ' the near future.'));
+ console.trace();
+ }
+
+ /**
+ Truncate a string to fit within an SVG text node
+ CSS text-overlow doesn't apply to SVG <= 1.2
+
+ @author Dan de Havilland (github.com/dandehavilland)
+ @date 2014-12-02
+ */
+ function truncate_text(textObj, textString, width) {
+ var bbox,
+ position = 0;
+
+ textObj.textContent = textString;
+ bbox = textObj.getBBox();
+
+ while (bbox.width > width) {
+ textObj.textContent = textString.slice(0, --position) + '...';
+ bbox = textObj.getBBox();
+
+ if (textObj.textContent === '...') {
+ break;
+ }
+ }
+ }
+
+
+ /**
+ Wrap the contents of a text node to a specific width
+
+ Adapted from bl.ocks.org/mbostock/7555321
+
+ @author Mike Bostock
+ @author Dan de Havilland
+ @date 2015-01-14
+ */
+ function wrapText(text, width, token, tspanAttrs) {
+ text.each(function() {
+ var text = d3.select(this),
+ words = text.text().split(token || /\s+/).reverse(),
+ word,
+ line = [],
+ lineNumber = 0,
+ lineHeight = 1.1, // ems
+ y = text.attr("y"),
+ dy = 0,
+ tspan = text.text(null)
+ .append("tspan")
+ .attr("x", 0)
+ .attr("y", dy + "em")
+ .attr(tspanAttrs || {});
+
+ while (!!(word = words.pop())) {
+ line.push(word);
+ tspan.text(line.join(" "));
+ if (width === null || tspan.node().getComputedTextLength() > width) {
+ line.pop();
+ tspan.text(line.join(" "));
+ line = [word];
+ tspan = text
+ .append("tspan")
+ .attr("x", 0)
+ .attr("y", ++lineNumber * lineHeight + dy + "em")
+ .attr(tspanAttrs || {})
+ .text(word);
+ }
+ }
+ });
+ }
+
+ //call this to add a warning icon to a graph and log an error to the console
+ function error(args) {
+ console.log('ERROR : ', args.target, ' : ', args.error);
+
+ d3.select(args.target).select('.mg-chart-title')
+ .append('i')
+ .attr('class', 'fa fa-x fa-exclamation-circle warning');
+ }
+
+ return MG;
+ }));
+}).call(this);
\ No newline at end of file
diff --git a/dist/metricsgraphics.min.js b/dist/metricsgraphics.min.js
new file mode 100644
index 0000000000..0dd662f047
--- /dev/null
+++ b/dist/metricsgraphics.min.js
@@ -0,0 +1,3 @@
+(function(){"use strict";!function(t,e){"function"==typeof define&&define.amd?define(["d3","jquery"],e):"object"==typeof exports?module.exports=e(require("d3"),require("jquery")):t.MG=e(t.d3,t.jQuery)}(this,function(t,e){function r(r){var a=t.select(r.target);if(a.select(".mg-chart-title").remove(),r.target&&r.title){var n=r.show_tooltips&&r.description?' ':"";if(a.insert("h2",":first-child").attr("class","mg-chart-title").html(r.title+n),r.show_tooltips&&r.description){var o=e(a.node()).find("h2.mg-chart-title");o.popover({html:!0,animation:!1,content:r.description,trigger:"hover",placement:"top",container:o})}}r.error&&L(r)}function a(t){for(var e=F(t.target),r="point"===t.chart_type?t.buffer/2:2*t.buffer/3,a=[],n=0;n0},u=function(t){return t[e.y_accessor]},d=0;d0){var p=t.extent(f,u);l?(o=Math.min(p[0],o),s=Math.max(p[1],s)):(o=p[0],s=p[1],l=!0)}}o>=0&&!e.min_y&&!e.min_y_from_data&&(o=0),"bar"===e.chart_type&&(o=0,s=t.max(e.data[0],function(t){var r=[];return r.push(t[e.y_accessor]),null!==e.baseline_accessor&&r.push(t[e.baseline_accessor]),null!==e.predictor_accessor&&r.push(t[e.predictor_accessor]),Math.max.apply(null,r)})),o=null!==e.min_y?e.min_y:o,s=null!==e.max_y?e.max_y:s*e.inflator,"log"!==e.y_scale_type&&(o>=0?e.y_axis_negative=!1:(o-=s*(e.inflator-1),e.y_axis_negative=!0)),!e.min_y&&e.min_y_from_data&&(o/=e.inflator),"log"===e.y_scale_type?("histogram"===e.chart_type?o=.2:0>=o&&(o=1),e.scales.Y=t.scale.log().domain([o,s]).range([e.height-e.bottom-e.buffer,e.top]).clamp(!0)):e.scales.Y=t.scale.linear().domain([o,s]).range([e.height-e.bottom-e.buffer,e.top]),e.processed.min_y=o,e.processed.max_y=s,e.scales.Y_axis=t.scale.linear().domain([e.processed.min_y,e.processed.max_y]).range([e.height-e.bottom-e.buffer,e.top]);var h=e.yax_format;if(h||(h="count"===e.format?function(r){if(1>r)return e.yax_units+t.round(r,e.decimals);var a=t.formatPrefix(r);return e.yax_units+a.scale(r)+a.symbol}:function(e){var r=t.format("%p");return r(e)}),i.selectAll(".mg-y-axis").remove(),!e.y_axis)return this;n=i.append("g").classed("mg-y-axis",!0).classed("mg-y-axis-small",e.use_small_class),e.y_label&&n.append("text").attr("class","label").attr("x",function(){return-1*(e.top+e.buffer+(e.height-e.bottom-e.buffer-(e.top+e.buffer))/2)}).attr("y",function(){return e.left/2}).attr("dy","0.4em").attr("text-anchor","middle").text(function(){return e.y_label}).attr("transform",function(){return"rotate(-90)"});var m=e.scales.Y.ticks(e.yax_count);"log"===e.y_scale_type&&(m=m.filter(function(t){return Math.abs(r(t))%1<1e-6||Math.abs(r(t))%1>1-1e-6}));var g=e.scales.Y.ticks(e.yax_count).length,_=!0;e.data.forEach(function(t){t.forEach(function(t){return t[e.y_accessor]%1!==0?(_=!1,!1):void 0})}),_&&g>s&&"count"===e.format&&(m=m.filter(function(t){return t%1===0}));var v=m.length-1;return e.x_extended_ticks||e.y_extended_ticks||n.append("line").attr("x1",e.left).attr("x2",e.left).attr("y1",e.scales.Y(m[0]).toFixed(2)).attr("y2",e.scales.Y(m[v]).toFixed(2)),n.selectAll(".mg-yax-ticks").data(m).enter().append("line").classed("mg-extended-y-ticks",e.y_extended_ticks).attr("x1",e.left).attr("x2",function(){return e.y_extended_ticks?e.width-e.right:e.left-e.yax_tick_length}).attr("y1",function(t){return e.scales.Y(t).toFixed(2)}).attr("y2",function(t){return e.scales.Y(t).toFixed(2)}),n.selectAll(".mg-yax-labels").data(m).enter().append("text").attr("x",e.left-3*e.yax_tick_length/2).attr("dx",-3).attr("y",function(t){return e.scales.Y(t).toFixed(2)}).attr("dy",".35em").attr("text-anchor","end").text(function(t){var e=h(t);return e}),e.y_rug&&a(e),this}function o(e){e.scales.Y=t.scale.ordinal().domain(e.categorical_variables).rangeRoundBands([e.height-e.bottom-e.buffer,e.top],e.padding_percentage,e.outer_padding_percentage),e.scalefns.yf=function(t){return e.scales.Y(t[e.y_accessor])};var r=F(e.target);r.selectAll(".mg-y-axis").remove();var a=r.append("g").classed("mg-y-axis",!0).classed("mg-y-axis-small",e.use_small_class);return e.y_axis?(a.selectAll("text").data(e.categorical_variables).enter().append("svg:text").attr("x",e.left).attr("y",function(t){return e.scales.Y(t)+e.scales.Y.rangeBand()/2+e.buffer*e.outer_padding_percentage}).attr("dy",".35em").attr("text-anchor","end").text(String),this):this}function s(t){for(var e="point"===t.chart_type?t.buffer/2:t.buffer,r=F(t.target),a=[],n=0;n10?t.scale.category20():t.scale.category10(),e.scales.color.domain(n)),e.scalefns.color=function(t){return e.scales.color(t[e.color_accessor])})}function u(e){var r,a,n,o;null!==e.size_accessor&&(null===e.size_domain?(r=t.min(e.data[0],function(t){return t[e.size_accessor]}),a=t.max(e.data[0],function(t){return t[e.size_accessor]}),n=[r,a]):n=e.size_domain,o=null===e.size_range?[1,5]:e.size_range,e.scales.size=t.scale.linear().domain(n).range(o).clamp(!0),e.scalefns.size=function(t){return e.scales.size(t[e.size_accessor])})}function d(t,e){t.append("text").attr("class","label").attr("x",function(){return(e.left+e.width-e.right)/2}).attr("y",(e.height-e.bottom/2).toFixed(2)).attr("dy",".50em").attr("text-anchor","middle").text(function(){return e.x_label})}function f(e){return e.xax_format?e.xax_format:function(r){if(1>r)return e.xax_units+t.round(r,e.decimals);var a=t.formatPrefix(r);return e.xax_units+a.scale(r)+a.symbol}}function p(e){if(e.xax_format)return e.xax_format;var r,a,n;return e.time_series&&(r=(e.processed.max_x-e.processed.min_x)/1e3,60>r?(a=t.time.format("%M:%S"),n="seconds"):24>=r/3600?(a=t.time.format("%H:%M"),n="less-than-a-day"):96>=r/3600?(a=t.time.format("%H:%M"),n="four-days"):(a=t.time.format("%b %d"),n="default")),e.processed.main_x_time_format=a,e.processed.x_time_frame=n,function(r){var a=(t.time.format("%b %d"),t.formatPrefix(r));return e.data[0][0][e.x_accessor]instanceof Date?e.processed.main_x_time_format(r):"number"==typeof e.data[0][0][e.x_accessor]?1>r?e.xax_units+t.round(r,e.decimals):(a=t.formatPrefix(r),e.xax_units+a.scale(r)+a.symbol):r}}function h(t,e){var r=e.scales.X.ticks(e.xax_count).length-1,a=e.scales.X.ticks(e.xax_count);e.xax_start_at_min&&(a[0]=e.processed.min_x),"bar"===e.chart_type||e.x_extended_ticks||e.y_extended_ticks||t.append("line").attr("x1",function(){return 0===e.xax_count?e.left+e.buffer:e.xax_start_at_min?e.scales.X(e.processed.min_x).toFixed(2):e.scales.X(e.scales.X.ticks(e.xax_count)[0]).toFixed(2)}).attr("x2",0===e.xax_count?e.width-e.right-e.buffer:e.scales.X(e.scales.X.ticks(e.xax_count)[r]).toFixed(2)).attr("y1",e.height-e.bottom).attr("y2",e.height-e.bottom),t.selectAll(".mg-xax-ticks").data(a).enter().append("line").attr("x1",function(t){return e.scales.X(t).toFixed(2)}).attr("x2",function(t){return e.scales.X(t).toFixed(2)}).attr("y1",e.height-e.bottom).attr("y2",function(){return e.x_extended_ticks?e.top:e.height-e.bottom+e.xax_tick_length}).attr("class",function(){return e.x_extended_ticks?"mg-extended-x-ticks":void 0})}function m(e,r){var a=r.scales.X.ticks(r.xax_count);if(r.xax_start_at_min&&(a[0]=r.processed.min_x),e.selectAll(".mg-xax-labels").data(a).enter().append("text").attr("x",function(t){return r.scales.X(t).toFixed(2)}).attr("y",(r.height-r.bottom+7*r.xax_tick_length/3).toFixed(2)).attr("dy",".50em").attr("text-anchor","middle").text(function(t){return r.xax_units+r.xax_format(t)}),r.time_series&&(r.show_years||r.show_secondary_x_label)){var n,o,s=r.processed.x_time_frame;switch(s){case"seconds":n=t.time.days,o=t.time.format("%I %p");break;case"less-than-a-day":n=t.time.days,o=t.time.format("%b %d");break;case"four-days":n=t.time.days,o=t.time.format("%b %d");break;default:n=t.time.years,o=t.time.format("%Y")}var i=n(r.processed.min_x,r.processed.max_x);if(r.xax_start_at_min&&0===i.length){var l=a[0];i=[l]}else if(0===i.length){var l=r.scales.X.ticks(r.xax_count)[0];i=[l]}e=e.append("g").classed("mg-year-marker",!0).classed("mg-year-marker-small",r.use_small_class),"default"===s&&r.show_year_markers&&e.selectAll(".mg-year-marker").data(i).enter().append("line").attr("x1",function(t){return r.scales.X(t).toFixed(2)}).attr("x2",function(t){return r.scales.X(t).toFixed(2)}).attr("y1",r.top).attr("y2",r.height-r.bottom),e.selectAll(".mg-year-marker").data(i).enter().append("text").attr("x",function(t,e){return r.xax_start_at_min&&0==e&&(t=a[0]),r.scales.X(t).toFixed(2)}).attr("y",(r.height-r.bottom+7*r.xax_tick_length/1.3).toFixed(2)).attr("dy",r.use_small_class?-3:0).attr("text-anchor","middle").text(function(t){return o(t)})}}function g(e){var r,a,n=[],o=[].concat.apply([],e.data),s=function(t){return t[e.x_accessor]};if(null===e.xax_format&&delete e.xax_format,"line"===e.chart_type||"point"===e.chart_type||"histogram"===e.chart_type?(n=t.extent(o,s),r=n[0],a=n[1]):"bar"===e.chart_type&&(r=0,a=t.max(o,function(t){var r=[t[e.x_accessor],t[e.baseline_accessor]?t[e.baseline_accessor]:0,t[e.predictor_accessor]?t[e.predictor_accessor]:0];return Math.max.apply(null,r)})),r===a){if(r instanceof Date){var i=MG.clone(r).setDate(r.getDate()-1),l=MG.clone(r).setDate(r.getDate()+1);r=i,a=l}else"number"==typeof r?(r-=1,a+=1):"string"==typeof r&&(r=Number(r)-1,a=Number(a)+1);e.xax_count=2}r=e.min_x?e.min_x:r,a=e.max_x?e.max_x:a,e.x_axis_negative=!1,e.processed.min_x=r,e.processed.max_x=a,_(e),e.time_series||e.processed.min_x<0&&(e.processed.min_x=e.processed.min_x-e.processed.max_x*(e.inflator-1),e.x_axis_negative=!0),e.additional_buffer="bar"===e.chart_type?5*e.buffer:0}function _(t){t.xax_format||"line"!==t.chart_type||(t.xax_format=p(t)),t.xax_format||"point"!==t.chart_type||(t.xax_format=p(t)),t.xax_format||"histogram"!==t.chart_type||(t.xax_format=p(t)),t.xax_format||"bar"!==t.chart_type||(t.xax_format=f(t))}function v(e){var a={target:null,title:null,description:null};if(e=arguments[0],e||(e={}),e=N(e,a),t.select(e.target).empty())return void console.warn('The specified target element "'+e.target+'" could not be found in the page. The chart will not be rendered.');var n=t.select(e.target),o=n.selectAll("svg");e.time_series=e.data[0][0][e.x_accessor]instanceof Date?!0:!1;var s=e.width,i=e.height;e.full_width&&(s=z(e.target)),e.full_height&&(i=E(e.target)),"bar"===e.chart_type&&null===i&&(i=e.height=e.data[0].length*e.bar_height+e.top+e.bottom),(!o.selectAll(".mg-main-line").empty()&&"line"!==e.chart_type||!o.selectAll(".mg-points").empty()&&"point"!==e.chart_type||!o.selectAll(".mg-histogram").empty()&&"histogram"!==e.chart_type||!o.selectAll(".mg-barplot").empty()&&"bar"!==e.chart_type)&&o.remove(),F(e.target).empty()&&(o=t.select(e.target).append("svg").classed("linked",e.linked).attr("width",s).attr("height",i)),e.width=s,e.height=i,o.selectAll(".mg-clip-path").remove(),o.append("defs").attr("class","mg-clip-path").append("clipPath").attr("id","mg-plot-window-"+C(e.target)).append("svg:rect").attr("x",e.left).attr("y",e.top).attr("width",e.width-e.left-e.right-e.buffer).attr("height",e.height-e.top-e.bottom-e.buffer+1),s!==Number(o.attr("width"))&&o.attr("width",s),i!==Number(o.attr("height"))&&o.attr("height",i),o.attr("viewBox","0 0 "+s+" "+i),(e.full_width||e.full_height)&&o.attr("preserveAspectRatio","xMinYMin meet"),o.classed("mg-missing",!1),o.selectAll(".mg-missing-text").remove(),o.selectAll(".mg-missing-pane").remove(),r(e),e.use_small_class=e.height-e.top-e.bottom-e.buffer<=e.small_height_threshold&&e.width-e.left-e.right-2*e.buffer<=e.small_width_threshold||e.small_text;var l=0;if(e.data.length0){var c=function(t){for(var e=new Array(t),r=0;rd;l--)o.selectAll(".mg-main-line.mg-line"+l+"-color").remove()}return this}function x(e){function r(r){if(r&&1!=r.length)for(var n=0;nr.right||n.righte.buffer+e.left&&e.scales.X(t[e.x_accessor])=c;c.setDate(c.getDate()+1)){var u={};c.setHours(0,0,0,0),Date.parse(c)===Date.parse(new Date(s))&&o.push(MG.clone(t.data[r][0]));var d=null;t.data[r].forEach(function(e){return Date.parse(e[t.x_accessor])===Date.parse(new Date(c))?(d=e,!1):void 0}),d?o.push(d):(u[t.x_accessor]=new Date(c),u[t.y_accessor]=0,u.missing=!0,o.push(u)),Date.parse(c)===Date.parse(new Date(n[t.x_accessor]))&&o.push(n)}t.data[r]=o}return this}function A(e){var r,a=e.data[0];if(e.binned===!1){if("object"==typeof a[0])r=a.map(function(t){return t[e.x_accessor]});else{if("number"!=typeof a[0])return void console.log("TypeError: expected an array of numbers, found "+typeof a[0]);r=a}var n=t.layout.histogram();e.bins&&(n=n.bins(e.bins)),e.processed_data=n(r).map(function(t){return{x:t.x,y:t.y,dx:t.dx}})}else{e.processed_data=a.map(function(t){return{x:t[e.x_accessor],y:t[e.y_accessor]}});for(var o,s,i=0;it?"M"+u.map(function(e){return e(t)}).join("L"):e}}}function F(e){return t.select(e).select("svg")}function C(t){var e;"string"==typeof t?e=t:""!=t.id?e=t.id:""!=args.target.className?e=t.className:""!=args.target.nodeName?e=t.nodeName:console.warn("The specified target element "+t+" has no unique attributes.");var r=e.replace(/[^a-zA-Z0-9 _]+/g,""),a=r.replace(/ +?/g,"");return a}function P(e,r){return Number(t.select(e).style(r).replace(/px/g,""))}function z(t){return P(t,"width")}function E(t){return P(t,"height")}function N(t){return I(Array.prototype.slice.call(arguments,1),function(e){if(e)for(var r in e)void 0===t[r]&&(t[r]=e[r])}),t}function $(t,e){var r,a=[],n=[];for(r=0;rr&&(t.textContent=e.slice(0,--n)+"...",a=t.getBBox(),"..."!==t.textContent););}function L(e){console.log("ERROR : ",e.target," : ",e.error),t.select(e.target).select(".mg-chart-title").append("i").attr("class","fa fa-x fa-exclamation-circle warning")}window.MG={version:"2.2.1"};var S={};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=!1,MG.globals.version="1.1",MG.data_graphic=function(){var e={};e.all={missing_is_zero:!1,missing_is_hidden:!1,legend:"",legend_target:"",error:"",animate_on_load:!1,top:40,bottom:30,right:10,left:50,buffer:8,width:350,height:220,full_width:!1,full_height:!1,small_height_threshold:120,small_width_threshold:160,small_text:!1,xax_count:6,xax_tick_length:5,xax_start_at_min:!1,yax_count:5,yax_tick_length:5,x_extended_ticks:!1,y_extended_ticks:!1,y_scale_type:"linear",max_x:null,max_y:null,min_x:null,min_y:null,min_y_from_data:!1,point_size:2.5,x_accessor:"date",xax_units:"",x_label:"",x_axis:!0,y_axis:!0,y_accessor:"value",y_label:"",yax_units:"",x_rug:!1,y_rug:!1,transition_on_update:!0,mouseover:null,show_rollover_text:!0,show_confidence_band:null,xax_format:null,area:!0,chart_type:"line",data:[],decimals:2,format:"count",inflator:10/9,linked:!1,linked_format:"%Y-%m-%d",list:!1,baselines:null,markers:null,scalefns:{},scales:{},show_year_markers:!1,show_secondary_x_label:!0,target:"#viz",interpolate:"cardinal",interpolate_tension:.7,custom_line_color_map:[],max_data_size:null,aggregate_rollover:!1,show_tooltips:!0},e.point={buffer:16,ls:!1,lowess:!1,point_size:2.5,size_accessor:null,color_accessor:null,size_range:null,color_range:null,size_domain:null,color_domain:null,color_type:"number"},e.histogram={mouseover:function(e){t.select("#histogram svg .mg-active-datapoint").text("Frequency Count: "+e.y)},binned:!1,bins:null,processed_x_accessor:"x",processed_y_accessor:"y",processed_dx_accessor:"dx",bar_margin:1},e.bar={y_accessor:"factor",x_accessor:"value",baseline_accessor:null,predictor_accessor:null,predictor_proportion:5,dodge_accessor:null,binned:!0,padding_percentage:0,outer_padding_percentage:.1,height:500,top:20,bar_height:20,left:70},e.missing={top:40,bottom:30,right:10,left:10,buffer:8,legend_target:"",width:350,height:220,missing_text:"Data currently missing or unavailable",scalefns:{},scales:{},show_missing_background:!0,interpolate:"cardinal"};var r=arguments[0];r||(r={}),r.list&&(r.x_accessor=0,r.y_accessor=1);for(var a in MG.deprecations)if(r.hasOwnProperty(a)){var n=MG.deprecations[a],o="Use of `args."+a+"` has been deprecated",s=n.replacement;if(s&&(r[s]?o+=". The replacement - `args."+s+"` - has already been defined. This definition will be discarded.":r[s]=r[a]),n.warned)continue;n.warned=!0,s&&(o+=" in favor of `args."+s+"`"),G(o,n.version)}var i;return"missing-data"===r.chart_type?(r=N(r,e.missing),S.missing(r)):"point"===r.chart_type?(i=N(e.point,e.all),r=N(r,i),S.point(r).mainPlot().markers().rollover().windowListeners()):"histogram"===r.chart_type?(i=N(e.histogram,e.all),r=N(r,i),S.histogram(r).mainPlot().markers().rollover().windowListeners()):"bar"===r.chart_type?(i=N(e.bar,e.all),r=N(r,i),S.bar(r).mainPlot().markers().rollover().windowListeners()):(r=N(r,e.all),S.line(r).markers().mainPlot().rollover().windowListeners()),r.data},"undefined"!=typeof jQuery&&(+function(t){function e(e){return this.each(function(){var a=t(this),n=a.data("bs.tooltip"),o="object"==typeof e&&e,s=o&&o.selector;(n||"destroy"!=e)&&(s?(n||a.data("bs.tooltip",n={}),n[s]||(n[s]=new r(this,o))):n||a.data("bs.tooltip",n=new r(this,o)),"string"==typeof e&&n[e]())})}if("function"==typeof t().tooltip)return!0;var r=function(t,e){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",t,e)};r.VERSION="3.3.1",r.TRANSITION_DURATION=150,r.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},r.prototype.init=function(e,r,a){this.enabled=!0,this.type=e,this.$element=t(r),this.options=this.getOptions(a),this.$viewport=this.options.viewport&&t(this.options.viewport.selector||this.options.viewport);for(var n=this.options.trigger.split(" "),o=n.length;o--;){var s=n[o];if("click"==s)this.$element.on("click."+this.type,this.options.selector,t.proxy(this.toggle,this));else if("manual"!=s){var i="hover"==s?"mouseenter":"focusin",l="hover"==s?"mouseleave":"focusout";this.$element.on(i+"."+this.type,this.options.selector,t.proxy(this.enter,this)),this.$element.on(l+"."+this.type,this.options.selector,t.proxy(this.leave,this))}}this.options.selector?this._options=t.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},r.prototype.getDefaults=function(){return r.DEFAULTS},r.prototype.getOptions=function(e){return e=t.extend({},this.getDefaults(),this.$element.data(),e),e.delay&&"number"==typeof e.delay&&(e.delay={show:e.delay,hide:e.delay}),e},r.prototype.getDelegateOptions=function(){var e={},r=this.getDefaults();return this._options&&t.each(this._options,function(t,a){r[t]!=a&&(e[t]=a)}),e},r.prototype.enter=function(e){var r=e instanceof this.constructor?e:t(e.currentTarget).data("bs."+this.type);return r&&r.$tip&&r.$tip.is(":visible")?void(r.hoverState="in"):(r||(r=new this.constructor(e.currentTarget,this.getDelegateOptions()),t(e.currentTarget).data("bs."+this.type,r)),clearTimeout(r.timeout),r.hoverState="in",r.options.delay&&r.options.delay.show?void(r.timeout=setTimeout(function(){"in"==r.hoverState&&r.show()},r.options.delay.show)):r.show())},r.prototype.leave=function(e){var r=e instanceof this.constructor?e:t(e.currentTarget).data("bs."+this.type);return r||(r=new this.constructor(e.currentTarget,this.getDelegateOptions()),t(e.currentTarget).data("bs."+this.type,r)),clearTimeout(r.timeout),r.hoverState="out",r.options.delay&&r.options.delay.hide?void(r.timeout=setTimeout(function(){"out"==r.hoverState&&r.hide()},r.options.delay.hide)):r.hide()},r.prototype.show=function(){var e=t.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(e);var a=t.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(e.isDefaultPrevented()||!a)return;var n=this,o=this.tip(),s=this.getUID(this.type);this.setContent(),o.attr("id",s),this.$element.attr("aria-describedby",s),this.options.animation&&o.addClass("fade");var i="function"==typeof this.options.placement?this.options.placement.call(this,o[0],this.$element[0]):this.options.placement,l=/\s?auto?\s?/i,c=l.test(i);c&&(i=i.replace(l,"")||"top"),o.detach().css({top:0,left:0,display:"block"}).addClass(i).data("bs."+this.type,this),this.options.container?o.appendTo(this.options.container):o.insertAfter(this.$element);var u=this.getPosition(),d=o[0].offsetWidth,f=o[0].offsetHeight;if(c){var p=i,h=this.options.container?t(this.options.container):this.$element.parent(),m=this.getPosition(h);i="bottom"==i&&u.bottom+f>m.bottom?"top":"top"==i&&u.top-fm.width?"left":"left"==i&&u.left-ds.top+s.height&&(n.top=s.top+s.height-l)}else{var c=e.left-o,u=e.left+o+r;cs.width&&(n.left=s.left+s.width-u)}return n},r.prototype.getTitle=function(){var t,e=this.$element,r=this.options;return t=e.attr("data-original-title")||("function"==typeof r.title?r.title.call(e[0]):r.title)},r.prototype.getUID=function(t){do t+=~~(1e6*Math.random());while(document.getElementById(t));return t},r.prototype.tip=function(){return this.$tip=this.$tip||t(this.options.template)},r.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},r.prototype.enable=function(){this.enabled=!0},r.prototype.disable=function(){this.enabled=!1},r.prototype.toggleEnabled=function(){this.enabled=!this.enabled},r.prototype.toggle=function(e){var r=this;e&&(r=t(e.currentTarget).data("bs."+this.type),r||(r=new this.constructor(e.currentTarget,this.getDelegateOptions()),t(e.currentTarget).data("bs."+this.type,r))),r.tip().hasClass("in")?r.leave(r):r.enter(r)},r.prototype.destroy=function(){var t=this;clearTimeout(this.timeout),this.hide(function(){t.$element.off("."+t.type).removeData("bs."+t.type)})};var a=t.fn.tooltip;t.fn.tooltip=e,t.fn.tooltip.Constructor=r,t.fn.tooltip.noConflict=function(){return t.fn.tooltip=a,this}}(jQuery),+function(t){function e(e){return this.each(function(){var a=t(this),n=a.data("bs.popover"),o="object"==typeof e&&e,s=o&&o.selector;(n||"destroy"!=e)&&(s?(n||a.data("bs.popover",n={}),n[s]||(n[s]=new r(this,o))):n||a.data("bs.popover",n=new r(this,o)),"string"==typeof e&&n[e]())})}if("function"==typeof t().popover)return!0;var r=function(t,e){this.init("popover",t,e)};if(!t.fn.tooltip)throw new Error("Popover requires tooltip.js");r.VERSION="3.3.1",r.DEFAULTS=t.extend({},t.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),r.prototype=t.extend({},t.fn.tooltip.Constructor.prototype),r.prototype.constructor=r,r.prototype.getDefaults=function(){return r.DEFAULTS},r.prototype.setContent=function(){var t=this.tip(),e=this.getTitle(),r=this.getContent();t.find(".popover-title")[this.options.html?"html":"text"](e),t.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof r?"html":"append":"text"](r),t.removeClass("fade top bottom left right in"),t.find(".popover-title").html()||t.find(".popover-title").hide()},r.prototype.hasContent=function(){return this.getTitle()||this.getContent()},r.prototype.getContent=function(){var t=this.$element,e=this.options;return t.attr("data-content")||("function"==typeof e.content?e.content.call(t[0]):e.content)},r.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},r.prototype.tip=function(){return this.$tip||(this.$tip=t(this.options.template)),this.$tip};var a=t.fn.popover;t.fn.popover=e,t.fn.popover.Constructor=r,t.fn.popover.noConflict=function(){return t.fn.popover=a,this}}(jQuery)),"undefined"!=typeof jQuery&&+function(t){function e(e){e&&3===e.which||(t(n).remove(),t(o).each(function(){var a=t(this),n=r(a),o={relatedTarget:this};n.hasClass("open")&&(n.trigger(e=t.Event("hide.bs.dropdown",o)),e.isDefaultPrevented()||(a.attr("aria-expanded","false"),n.removeClass("open").trigger("hidden.bs.dropdown",o)))}))}function r(e){var r=e.attr("data-target");r||(r=e.attr("href"),r=r&&/#[A-Za-z]/.test(r)&&r.replace(/.*(?=#[^\s]*$)/,""));var a=r&&t(r);return a&&a.length?a:e.parent()}function a(e){return this.each(function(){var r=t(this),a=r.data("bs.dropdown");a||r.data("bs.dropdown",a=new s(this)),"string"==typeof e&&a[e].call(r)})}if("function"==typeof t().dropdown)return!0;var n=".dropdown-backdrop",o='[data-toggle="dropdown"]',s=function(e){t(e).on("click.bs.dropdown",this.toggle)};s.VERSION="3.3.1",s.prototype.toggle=function(a){var n=t(this);if(!n.is(".disabled, :disabled")){var o=r(n),s=o.hasClass("open");if(e(),!s){"ontouchstart"in document.documentElement&&!o.closest(".navbar-nav").length&&t('
').insertAfter(t(this)).on("click",e);var i={relatedTarget:this};if(o.trigger(a=t.Event("show.bs.dropdown",i)),a.isDefaultPrevented())return;n.trigger("focus").attr("aria-expanded","true"),o.toggleClass("open").trigger("shown.bs.dropdown",i)}return!1}},s.prototype.keydown=function(e){if(/(38|40|27|32)/.test(e.which)&&!/input|textarea/i.test(e.target.tagName)){var a=t(this);if(e.preventDefault(),e.stopPropagation(),!a.is(".disabled, :disabled")){var n=r(a),s=n.hasClass("open");if(!s&&27!=e.which||s&&27==e.which)return 27==e.which&&n.find(o).trigger("focus"),a.trigger("click");var i=" li:not(.divider):visible a",l=n.find('[role="menu"]'+i+', [role="listbox"]'+i);if(l.length){var c=l.index(e.target);38==e.which&&c>0&&c--,40==e.which&&c1&&(this.public_name[t]=arguments[1]),arguments.length>2&&(this.sorters[t]=arguments[2]),this.feature_set[t]=[],this},this.callback=function(t){return this._callback=t,this},this.display=function(){var t,r,a,n,o=this._callback,s=this.manual_callback,i=this.manual_map;a=Object.keys(this.feature_set);var l,c=function(e){return t[e]};for(l=0;l");var d=function(){var t,r=e(this).data("key"),a=e(this).data("feature");return e("."+a+"-btns button.btn span.title").html(r),i.hasOwnProperty(a)?(t=i[a],s[t](r)):o(a,r),!1};for(var f in this.feature_set){for(a=this.feature_set[f],e(this.target+" div.segments").append(''+(this.public_name.hasOwnProperty(f)?this.public_name[f]:f)+" "+(this.manual_callback.hasOwnProperty(f)?this.feature_set[f][0]:"all")+'
"),l=0;l'+a[l]+" ");e("."+this._strip_punctuation(f)+"-btns .dropdown-menu li a").on("click",d)}return this},this},S.line=function(e){return this.args=e,this.init=function(t){return w(t),k(t),v(t),i(t),n(t),this},this.mainPlot=function(){var r,a=F(e.target),n=0,o=e.transition_on_update?1e3:0,s=function(t){return t[e.y_accessor]},i=t.svg.area().x(e.scalefns.xf).y0(e.scales.Y.range()[0]).y1(e.scalefns.yf).interpolate(e.interpolate).tension(e.interpolate_tension),l=a.select(".mg-confidence-band");e.show_confidence_band&&(r=t.svg.area().x(e.scalefns.xf).y0(function(t){var r=e.show_confidence_band[0];return e.scales.Y(t[r])}).y1(function(t){var r=e.show_confidence_band[1];return e.scales.Y(t[r])}).interpolate(e.interpolate).tension(e.interpolate_tension));for(var c,u,d=t.svg.line().x(e.scalefns.xf).y(e.scalefns.yf).interpolate(e.interpolate).tension(e.interpolate_tension),f=t.svg.line().x(e.scalefns.xf).y(function(){return e.scales.Y(n)}).interpolate(e.interpolate).tension(e.interpolate_tension),p="",h=e.data.length-1;h>=0;h--){c=e.data[h];var m=h+1;e.custom_line_color_map.length>0&&(m=e.custom_line_color_map[h]),e.data[h].line_id=m,e.show_confidence_band&&(u=l.empty()?a.append("path").attr("class","mg-confidence-band"):l.transition().duration(function(){return e.transition_on_update?1e3:0}),u.attr("d",r(e.data[h])).attr("clip-path","url(#mg-plot-window-"+C(e.target)+")"));var g=a.selectAll(".mg-area"+m+"-color"),_=e.area&&!e.use_data_y_min&&!e.y_axis_negative&&e.data.length<=1;_?g.empty()?a.append("path").attr("class","mg-main-area mg-area"+m+"-color").attr("d",i(e.data[h])).attr("clip-path","url(#mg-plot-window-"+C(e.target)+")"):(a.select(".mg-y-axis").node().parentNode.appendChild(g.node()),g.transition().duration(o).attr("d",i(e.data[h])).attr("clip-path","url(#mg-plot-window-"+C(e.target)+")")):g.empty()||g.remove();var v=a.select("path.mg-main-line.mg-line"+m+"-color");if(v.empty())e.animate_on_load?(n=t.median(e.data[h],s),a.append("path").attr("class","mg-main-line mg-line"+m+"-color").attr("d",f(e.data[h])).transition().duration(1e3).attr("d",d(e.data[h])).attr("clip-path","url(#mg-plot-window-"+C(e.target)+")")):a.append("path").attr("class","mg-main-line mg-line"+m+"-color").attr("d",d(e.data[h])).attr("clip-path","url(#mg-plot-window-"+C(e.target)+")");else{a.select(".mg-y-axis").node().parentNode.appendChild(v.node());var x=v.transition().duration(o);_?x.attr("d",d(e.data[h])):x.attrTween("d",X(d(e.data[h]),4))}var y=a.select(".mg-line"+m+"-color");if(e.missing_is_hidden&&null!==y.attr("d")){var b,w,k,A,D,M,O,Y=y.attr("d").split("L"),T=e.scales.Y(0)+42.1234,P=[],z=2,E=0;Y[0]=Y[0].replace("M",""),Y[Y.length-1]=Y[Y.length-1].replace("Z",""),e.min_x&&P.push(0);for(var N=0;N0&&(P.push(y.node().getTotalLength()-P[P.length-1]),a.select(".mg-line"+m+"-color").attr("stroke-dasharray",P.join()))}e.legend&&(p="— "+e.legend[h]+" "+p)}return e.legend&&t.select(e.legend_target).html(p),this},this.markers=function(){return x(e),this},this.rollover=function(){var r,a=F(e.target);a.selectAll(".mg-rollover-rect").remove(),a.selectAll(".mg-voronoi").remove(),a.selectAll(".mg-active-datapoint").remove(),a.selectAll(".mg-line-rollover-circle").remove(),a.selectAll(".mg-active-datapoint-container").remove(),a.append("g").attr("class","mg-active-datapoint-container").attr("transform","translate("+(e.width-e.right)+","+e.top/2+")").append("text").attr("class","mg-active-datapoint").classed("mg-active-datapoint-small",e.use_small_class).attr("xml:space","preserve").attr("text-anchor","end"),a.selectAll(".mg-line-rollover-circle").data(e.data).enter().append("circle").attr({"class":function(t){return["mg-line-rollover-circle","mg-line"+t.line_id+"-color","mg-area"+t.line_id+"-color"].join(" ")},cx:0,cy:0,r:0});for(var n=1,o=0;o0?e.custom_line_color_map[o]:n;n++}var i,l;if(e.data.length>1&&!e.aggregate_rollover){var c=t.geom.voronoi().x(function(t){return e.scales.X(t[e.x_accessor]).toFixed(2)}).y(function(t){return e.scales.Y(t[e.y_accessor]).toFixed(2)}).clipExtent([[e.buffer,e.buffer],[e.width-e.buffer,e.height-e.buffer]]);r=a.append("g").attr("class","mg-voronoi"),i=t.nest().key(function(t){return e.scales.X(t[e.x_accessor])+","+e.scales.Y(t[e.y_accessor])}).rollup(function(t){return t[0]}).entries(t.merge(e.data.map(function(t){return t}))).map(function(t){return t.values}),r.selectAll("path").data(c(i)).enter().append("path").filter(function(t){return void 0!==t}).attr("d",function(t){return"M"+t.join("L")+"Z"}).datum(function(t){return t.point}).attr("class",function(r){if(e.linked){var a=r[e.x_accessor],n=t.time.format(e.linked_format),s="number"==typeof a?o:n(a);return"mg-line"+r.line_id+"-color roll_"+s}return"mg-line"+r.line_id+"-color"}).on("mouseover",this.rolloverOn(e)).on("mouseout",this.rolloverOff(e)).on("mousemove",this.rolloverMove(e))}else e.data.length>1&&e.aggregate_rollover?(i=t.nest().key(function(t){return t[e.x_accessor]}).entries(t.merge(e.data)),l=i.map(function(t){return e.scales.X(new Date(t.key))}),r=a.append("g").attr("class","mg-rollover-rect"),r.selectAll(".mg-rollover-rects").data(i).enter().append("rect").attr("x",function(t,r){return 1===l.length?e.left+e.buffer:0===r?l[r].toFixed(2):((l[r-1]+l[r])/2).toFixed(2)}).attr("y",e.top).attr("width",function(t,r){return 1===l.length?e.width-e.right-e.buffer:0===r?((l[r+1]-l[r])/2).toFixed(2):r==l.length-1?((l[r]-l[r-1])/2).toFixed(2):((l[r+1]-l[r-1])/2).toFixed(2)}).attr("height",e.height-e.bottom-e.top-e.buffer).attr("opacity",0).on("mouseover",this.rolloverOn(e)).on("mouseout",this.rolloverOff(e)).on("mousemove",this.rolloverMove(e))):(n=1,e.custom_line_color_map.length>0&&(n=e.custom_line_color_map[0]),r=a.append("g").attr("class","mg-rollover-rect"),l=e.data[0].map(e.scalefns.xf),r.selectAll(".mg-rollover-rects").data(e.data[0]).enter().append("rect").attr("class",function(r,a){if(e.linked){var o=r[e.x_accessor],s=t.time.format(e.linked_format),i="number"==typeof o?a:s(o);return"mg-line"+n+"-color roll_"+i}return"mg-line"+n+"-color"}).attr("x",function(t,r){return 1===l.length?e.left+e.buffer:0===r?l[r].toFixed(2):((l[r-1]+l[r])/2).toFixed(2)}).attr("y",function(t){return e.data.length>1?e.scalefns.yf(t)-6:e.top}).attr("width",function(t,r){return 1===l.length?e.width-e.right-e.buffer:0===r?((l[r+1]-l[r])/2).toFixed(2):r===l.length-1?((l[r]-l[r-1])/2).toFixed(2):((l[r+1]-l[r-1])/2).toFixed(2)}).attr("height",function(){return e.data.length>1?12:e.height-e.bottom-e.top-e.buffer}).attr("opacity",0).on("mouseover",this.rolloverOn(e)).on("mouseout",this.rolloverOff(e)).on("mousemove",this.rolloverMove(e)));if(1==e.data.length&&1==e.data[0].length)a.select(".mg-rollover-rect rect").on("mouseover")(e.data[0][0],0);else if(e.data.length>1)for(var o=0;o1)a.selectAll("circle.mg-line-rollover-circle").style("opacity",0),n.values.forEach(function(t){if(t[e.x_accessor]>=e.processed.min_x&&t[e.x_accessor]<=e.processed.max_x&&t[e.y_accessor]>=e.processed.min_y&&t[e.y_accessor]<=e.processed.max_y){a.select("circle.mg-line"+t.line_id+"-color").attr({cx:function(){return e.scales.X(t[e.x_accessor]).toFixed(2)},cy:function(){return e.scales.Y(t[e.y_accessor]).toFixed(2)},r:e.point_size}).style("opacity",1)}});else{if(e.missing_is_hidden&&0==n[e.y_accessor]&&n.missing)return;if(n[e.x_accessor]>=e.processed.min_x&&n[e.x_accessor]<=e.processed.max_x&&n[e.y_accessor]>=e.processed.min_y&&n[e.y_accessor]<=e.processed.max_y&&a.selectAll("circle.mg-line-rollover-circle").attr("class","").attr("class","mg-area"+n.line_id+"-color").classed("mg-line-rollover-circle",!0).attr("cx",function(){return e.scales.X(n[e.x_accessor]).toFixed(2)}).attr("cy",function(){return e.scales.Y(n[e.y_accessor]).toFixed(2)}).attr("r",e.point_size).style("opacity",1),e.linked&&!MG.globals.link){MG.globals.link=!0;var s=n[e.x_accessor],i=t.time.format(e.linked_format),l="number"==typeof s?o:i(s);t.selectAll(".mg-line"+n.line_id+"-color.roll_"+l).each(function(e,r){t.select(this).on("mouseover")(e,r)})}}a.selectAll("text").filter(function(t){return n===t}).attr("opacity",.3);var c=T(e);if(e.show_rollover_text){var u=a.select(".mg-active-datapoint"),d=0,f=1.1;if(u.select("*").remove(),e.aggregate_rollover&&e.data.length>1){if(e.time_series){var p=new Date(n.key);u.append("tspan").text((r(p)+" "+e.yax_units).trim()),d=1,n.values.forEach(function(t){var r=u.append("tspan").attr({x:0,y:d*f+"em"}).text(c(t[e.y_accessor]));u.append("tspan").attr({x:-r.node().getComputedTextLength(),y:d*f+"em"}).text("— ").classed("mg-hover-line"+t.line_id+"-color",!0).style("font-weight","bold"),d++}),u.append("tspan").attr("x",0).attr("y",d*f+"em").text(" ")}else n.values.forEach(function(t){var r=u.append("tspan").attr({x:0,y:d*f+"em"}).text(e.x_accessor+": "+t[e.x_accessor]+", "+e.y_accessor+": "+e.yax_units+c(t[e.y_accessor]));u.append("tspan").attr({x:-r.node().getComputedTextLength(),y:d*f+"em"}).text("— ").classed("mg-hover-line"+t.line_id+"-color",!0).style("font-weight","bold"),d++});u.append("tspan").attr("x",0).attr("y",d*f+"em").text(" ")}else if(e.time_series){var h=new Date(+n[e.x_accessor]);h.setDate(h.getDate()),u.append("tspan").text(r(h)+" "+e.yax_units+c(n[e.y_accessor]))}else u.append("tspan").text(e.x_accessor+": "+n[e.x_accessor]+", "+e.y_accessor+": "+e.yax_units+c(n[e.y_accessor]))}e.mouseover&&e.mouseover(n,o)}},this.rolloverOff=function(e){var r=F(e.target);return function(a,n){if(e.linked&&MG.globals.link){MG.globals.link=!1;var o=a[e.x_accessor],s=t.time.format(e.linked_format),i="number"==typeof o?n:s(o);t.selectAll(".roll_"+i).each(function(e){t.select(this).on("mouseout")(e)})}r.selectAll("circle.mg-line-rollover-circle").style("opacity",function(){return 1==e.data.length&&1==e.data[0].length?1:0}),r.select(".mg-active-datapoint").text(""),e.mouseout&&e.mouseout(a,n)}},this.rolloverMove=function(t){return function(e,r){t.mousemove&&t.mousemove(e,r)}},this.windowListeners=function(){return y(this.args),this},this.init(e),this},S.histogram=function(e){return this.args=e,this.init=function(t){return w(t),A(t),v(t),i(t),n(t),this},this.mainPlot=function(){var t=F(e.target);t.selectAll(".mg-histogram").remove();var r=t.append("g").attr("class","mg-histogram"),a=r.selectAll(".mg-bar").data(e.data[0]).enter().append("g").attr("class","mg-bar").attr("transform",function(t){return"translate("+e.scales.X(t[e.x_accessor]).toFixed(2)+","+e.scales.Y(t[e.y_accessor]).toFixed(2)+")"});return a.append("rect").attr("x",1).attr("width",function(){return 1===e.data[0].length?(e.scalefns.xf(e.data[0][0])-e.bar_margin).toFixed(2):(e.scalefns.xf(e.data[0][1])-e.scalefns.xf(e.data[0][0])-e.bar_margin).toFixed(2)}).attr("height",function(t){return 0===t[e.y_accessor]?0:(e.height-e.bottom-e.buffer-e.scales.Y(t[e.y_accessor])).toFixed(2)}),this},this.markers=function(){return x(e),this},this.rollover=function(){var t=F(e.target);t.selectAll(".mg-rollover-rect").remove(),t.selectAll(".mg-active-datapoint").remove(),t.append("text").attr("class","mg-active-datapoint").attr("xml:space","preserve").attr("x",e.width-e.right).attr("y",e.top/2).attr("text-anchor","end");var r=t.append("g").attr("class","mg-rollover-rect"),a=r.selectAll(".mg-bar").data(e.data[0]).enter().append("g").attr("class",function(t,r){return e.linked?"mg-rollover-rects roll_"+r:"mg-rollover-rects"}).attr("transform",function(t){return"translate("+e.scales.X(t[e.x_accessor])+",0)"});return a.append("rect").attr("x",1).attr("y",0).attr("width",function(t,r){return 1===e.data[0].length?(e.scalefns.xf(e.data[0][0])-e.bar_margin).toFixed(2):r!==e.data[0].length-1?(e.scalefns.xf(e.data[0][r+1])-e.scalefns.xf(t)).toFixed(2):(e.scalefns.xf(e.data[0][1])-e.scalefns.xf(e.data[0][0])).toFixed(2)}).attr("height",function(){return e.height}).attr("opacity",0).on("mouseover",this.rolloverOn(e)).on("mouseout",this.rolloverOff(e)).on("mousemove",this.rolloverMove(e)),this},this.rolloverOn=function(e){{var r=F(e.target);t.time.format("%Y-%m-%d")}return function(a,n){r.selectAll("text").filter(function(t){return a===t}).attr("opacity",.3);var o=t.time.format("%b %e, %Y"),s=T(e);r.selectAll(".mg-bar rect").filter(function(t,e){return e===n}).classed("active",!0),e.linked&&!MG.globals.link&&(MG.globals.link=!0,t.selectAll(".mg-rollover-rects.roll_"+n+" rect").each(function(e){t.select(this).on("mouseover")(e,n)})),e.show_rollover_text&&r.select(".mg-active-datapoint").text(function(){if(e.time_series){var t=new Date(+a[e.x_accessor]);return t.setDate(t.getDate()),o(t)+" "+e.yax_units+s(a[e.y_accessor])}return e.x_accessor+": "+s(a[e.x_accessor])+", "+e.y_accessor+": "+e.yax_units+s(a[e.y_accessor])}),e.mouseover&&e.mouseover(a,n)}},this.rolloverOff=function(e){var r=F(e.target);return function(a,n){e.linked&&MG.globals.link&&(MG.globals.link=!1,t.selectAll(".mg-rollover-rects.roll_"+n+" rect").each(function(e){t.select(this).on("mouseout")(e,n)})),r.selectAll(".mg-bar rect").classed("active",!1),r.select(".mg-active-datapoint").text(""),e.mouseout&&e.mouseout(a,n)}},this.rolloverMove=function(t){return function(e,r){t.mousemove&&t.mousemove(e,r)}},this.windowListeners=function(){return y(this.args),this},this.init(e),this},S.point=function(e){return this.args=e,this.init=function(t){return w(t),M(t),v(t),i(t),n(t),this},this.markers=function(){return x(e),e.least_squares&&O(e),this},this.mainPlot=function(){var t,r=F(e.target);r.selectAll(".mg-points").remove(),t=r.append("g").classed("mg-points",!0);var a=t.selectAll("circle").data(e.data[0]).enter().append("svg:circle").attr("class",function(t,e){return"path-"+e}).attr("cx",e.scalefns.xf).attr("cy",e.scalefns.yf);return null!==e.color_accessor?(a.attr("fill",e.scalefns.color),a.attr("stroke",e.scalefns.color)):a.classed("mg-points-mono",!0),null!==e.size_accessor?a.attr("r",e.scalefns.size):a.attr("r",e.point_size),this},this.rollover=function(){var r=F(e.target);r.selectAll(".mg-voronoi").remove(),r.selectAll(".mg-active-datapoint").remove(),r.append("text").attr("class","mg-active-datapoint").attr("xml:space","preserve").attr("x",e.width-e.right).attr("y",e.top/2).attr("text-anchor","end");var a=t.geom.voronoi().x(e.scalefns.xf).y(e.scalefns.yf).clipExtent([[e.buffer,e.buffer],[e.width-e.buffer,e.height-e.buffer]]),n=r.append("g").attr("class","mg-voronoi");return n.selectAll("path").data(a(e.data[0])).enter().append("path").attr("d",function(t){return void 0!==t?"M"+t.join(",")+"Z":void 0}).attr("class",function(t,e){return"path-"+e}).style("fill-opacity",0).on("mouseover",this.rolloverOn(e)).on("mouseout",this.rolloverOff(e)).on("mousemove",this.rolloverMove(e)),this},this.rolloverOn=function(e){var r=F(e.target);return function(a,n){r.selectAll(".mg-points circle").classed("selected",!1);var o=r.selectAll(".mg-points circle.path-"+n).classed("selected",!0);e.size_accessor?o.attr("r",function(t){return e.scalefns.size(t)+1}):o.attr("r",e.point_size),e.linked&&!globals.link&&(globals.link=!0,t.selectAll(".mg-voronoi .path-"+n).each(function(){t.select(this).on("mouseover")(a,n)}));var s=t.time.format("%b %e, %Y"),i=T(e);e.show_rollover_text&&r.select(".mg-active-datapoint").text(function(){if(e.time_series){var t=new Date(+a.point[e.x_accessor]);return t.setDate(t.getDate()),s(t)+" "+e.yax_units+i(a.point[e.y_accessor])}return e.x_accessor+": "+i(a.point[e.x_accessor])+", "+e.y_accessor+": "+e.yax_units+i(a.point[e.y_accessor])}),e.mouseover&&e.mouseover(a,n)}},this.rolloverOff=function(e){var r=F(e.target);return function(a,n){e.linked&&globals.link&&(globals.link=!1,t.selectAll(".mg-voronoi .path-"+n).each(function(){t.select(this).on("mouseout")(a,n)}));var o=r.selectAll(".mg-points circle").classed("unselected",!1).classed("selected",!1);e.size_accessor?o.attr("r",e.scalefns.size):o.attr("r",e.point_size),r.select(".mg-active-datapoint").text(""),e.mouseout&&e.mouseout(a,n)}},this.rolloverMove=function(t){return function(e,r){t.mousemove&&t.mousemove(e,r)}},this.update=function(){return this},this.windowListeners=function(){return y(this.args),this},this.init(e),this},S.bar=function(e){return this.args=e,this.is_vertical=!0,this.init=function(t){return w(t),D(t),v(t),this.is_vertical="vertical"===t.bar_orientation,this.is_vertical?(l(t),n(t)):(i(t),o(t)),this},this.mainPlot=function(){var t,r,a,n,o,s=F(e.target),i=e.data[0],l=s.select("g.mg-barplot"),c=l.empty(),u=c&&e.animate_on_load,d=u||e.transition_on_update,f=e.transition_duration||1e3;l.empty()&&(l=s.append("g").classed("mg-barplot",!0)),t=t=l.selectAll(".mg-bar").data(i),t.exit().remove(),t.enter().append("rect").classed("mg-bar",!0),e.predictor_accessor&&(r=l.selectAll(".mg-bar-prediction").data(i),r.exit().remove(),r.enter().append("rect").classed("mg-bar-prediction",!0)),e.baseline_accessor&&(o=l.selectAll(".mg-bar-baseline").data(i),o.exit().remove(),o.enter().append("line").classed("mg-bar-baseline",!0));var p;return d&&(t=t.transition().duration(f),r&&(r=r.transition().duration(f)),o&&(o=o.transition().duration(f))),this.is_vertical?(p=e.scales.X.rangeBand()/1.5,u&&(t.attr({height:0,y:e.scales.Y(0)}),r&&r.attr({height:0,y:e.scales.Y(0)}),o&&o.attr({y1:e.scales.Y(0),y2:e.scales.Y(0)})),t.attr("y",e.scalefns.yf).attr("x",function(t){return e.scalefns.xf(t)+p/2}).attr("width",p).attr("height",function(t){return 0-(e.scalefns.yf(t)-e.scales.Y(0))}),e.predictor_accessor&&(a=e.predictor_proportion,n=a-1,r.attr("y",function(t){return e.scales.Y(0)-(e.scales.Y(0)-e.scales.Y(t[e.predictor_accessor]))}).attr("x",function(t){return e.scalefns.xf(t)+n*p/(2*a)+p/2}).attr("width",p/a).attr("height",function(t){return 0-(e.scales.Y(t[e.predictor_accessor])-e.scales.Y(0))})),e.baseline_accessor&&(a=e.predictor_proportion,o.attr("x1",function(t){return e.scalefns.xf(t)+p/2-p/a+p/2}).attr("x2",function(t){return e.scalefns.xf(t)+p/2+p/a+p/2}).attr("y1",function(t){return e.scales.Y(t[e.baseline_accessor])}).attr("y2",function(t){return e.scales.Y(t[e.baseline_accessor])}))):(p=e.scales.Y.rangeBand()/1.5,u&&(t.attr("width",0),r&&r.attr("width",0),o&&o.attr({x1:e.scales.X(0),x2:e.scales.X(0)})),t.attr("x",e.scales.X(0)).attr("y",function(t){return e.scalefns.yf(t)+p/2}).attr("height",p).attr("width",function(t){return e.scalefns.xf(t)-e.scales.X(0)}),e.predictor_accessor&&(a=e.predictor_proportion,n=a-1,r.attr("x",e.scales.X(0)).attr("y",function(t){return e.scalefns.yf(t)+n*p/(2*a)+p/2}).attr("height",p/a).attr("width",function(t){return e.scales.X(t[e.predictor_accessor])-e.scales.X(0)})),e.baseline_accessor&&(a=e.predictor_proportion,o.attr("x1",function(t){return e.scales.X(t[e.baseline_accessor])}).attr("x2",function(t){return e.scales.X(t[e.baseline_accessor])}).attr("y1",function(t){return e.scalefns.yf(t)+p/2-p/a+p/2}).attr("y2",function(t){return e.scalefns.yf(t)+p/2+p/a+p/2}))),this},this.markers=function(){return x(e),this},this.rollover=function(){var t,r=F(e.target);r.selectAll(".mg-rollover-rect").remove(),r.selectAll(".mg-active-datapoint").remove(),r.append("text").attr("class","mg-active-datapoint").attr("xml:space","preserve").attr("x",e.width-e.right).attr("y",e.top/2).attr("dy",".35em").attr("text-anchor","end"),t=r.append("g").attr("class","mg-rollover-rect");var a=t.selectAll(".mg-bar-rollover").data(e.data[0]).enter().append("rect").attr("class","mg-bar-rollover");return this.is_vertical?a.attr("x",e.scalefns.xf).attr("y",function(){return e.scales.Y(0)-e.height}).attr("width",e.scales.X.rangeBand()).attr("height",e.height).attr("opacity",0).on("mouseover",this.rolloverOn(e)).on("mouseout",this.rolloverOff(e)).on("mousemove",this.rolloverMove(e)):a.attr("x",e.scales.X(0)).attr("y",e.scalefns.yf).attr("width",e.width).attr("height",e.scales.Y.rangeBand()+2).attr("opacity",0).on("mouseover",this.rolloverOn(e)).on("mouseout",this.rolloverOff(e)).on("mousemove",this.rolloverMove(e)),this},this.rolloverOn=function(e){var r=F(e.target),a=this.is_vertical?e.x_accessor:e.y_accessor,n=this.is_vertical?e.y_accessor:e.x_accessor,o=this.is_vertical?e.yax_units:e.xax_units;return function(s,i){r.selectAll("text").filter(function(t){return s===t}).attr("opacity",.3);var l=t.time.format("%b %e, %Y"),c=T(e);r.selectAll("g.mg-barplot .mg-bar").filter(function(t,e){return e===i}).classed("active",!0),e.show_rollover_text&&r.select(".mg-active-datapoint").text(function(){if(e.time_series){var t=new Date(+s[n]);return t.setDate(t.getDate()),l(t)+" "+o+c(s[a])}return s[a]+": "+c(s[n])}),e.mouseover&&e.mouseover(s,i)}},this.rolloverOff=function(t){var e=F(t.target);return function(r,a){e.selectAll("g.mg-barplot .mg-bar").classed("active",!1),e.select(".mg-active-datapoint").text(""),t.mouseout&&t.mouseout(r,a)}},this.rolloverMove=function(t){return function(e,r){t.mousemove&&t.mousemove(e,r)}},this.windowListeners=function(){return y(this.args),this},this.init(e),this},MG.data_table=function(a){return this.args=a,this.args.standard_col={width:150,font_size:12,font_weight:"normal"},this.args.columns=[],this.formatting_options=[["color","color"],["font-weight","font_weight"],["font-style","font_style"],["font-size","font_size"]],this._strip_punctuation=function(t){var e=t.replace(/[^a-zA-Z0-9 _]+/g,""),r=e.replace(/ +?/g,"");return r},this._format_element=function(t,e,r){this.formatting_options.forEach(function(a){var n=a[0],o=a[1];r[o]&&t.style(n,"string"==typeof r[o]||"number"==typeof r[o]?r[o]:r[o](e))})},this._add_column=function(t,e){var r=this.args.standard_col,a=N(MG.clone(t),MG.clone(r));a.type=e,this.args.columns.push(a)},this.target=function(){var t=arguments[0];return this.args.target=t,this},this.title=function(){return this._add_column(arguments[0],"title"),this},this.text=function(){return this._add_column(arguments[0],"text"),this},this.bullet=function(){return this},this.sparkline=function(){return this},this.number=function(){return this._add_column(arguments[0],"number"),this},this.display=function(){var a=this.args;r(a);var n,o,s,i,l,c,u,d,f,p,h,m,g=a.target,_=t.select(g).append("table").classed("mg-data-table",!0),v=_.append("colgroup"),x=_.append("thead"),y=_.append("tbody");for(s=x.append("tr"),m=0;m=o;o++)n.push({x:o,y:Math.random()-.03*o});e.scales.X=t.scale.linear().domain([0,n.length]).range([e.left+e.buffer,e.width-e.right-e.buffer]),e.scales.Y=t.scale.linear().domain([-2,2]).range([e.height-e.bottom-2*e.buffer,e.top]),e.scalefns.xf=function(t){return e.scales.X(t.x)},e.scalefns.yf=function(t){return e.scales.Y(t.y)};var s=t.svg.line().x(e.scalefns.xf).y(e.scalefns.yf).interpolate(e.interpolate),i=t.svg.area().x(e.scalefns.xf).y0(e.scales.Y.range()[0]).y1(e.scalefns.yf).interpolate(e.interpolate),l=a.append("g").attr("class","mg-missing-pane");l.append("svg:rect").classed("mg-missing-background",!0).attr("x",e.buffer).attr("y",e.buffer).attr("width",e.width-2*e.buffer).attr("height",e.height-2*e.buffer).attr("rx",15).attr("ry",15),l.append("path").attr("class","mg-main-line mg-line1-color").attr("d",s(n)),l.append("path").attr("class","mg-main-area mg-area1-color").attr("d",i(n))}return a.selectAll(".mg-missing-text").data([e.missing_text]).enter().append("text").attr("class","mg-missing-text").attr("x",e.width/2).attr("y",e.height/2).attr("dy",".50em").attr("text-anchor","middle").text(e.missing_text),this},this.init(e),this},MG.convert={},MG.convert.date=function(e,r,a){return a="undefined"==typeof a?"%Y-%m-%d":a,e=e.map(function(e){var n=t.time.format(a);return e[r]=n.parse(e[r]),e})},MG.convert.number=function(t,e){return t=t.map(function(t){return t[e]=Number(t[e]),t})};var I=function(t,e,r){if(null===t)return t;if(Array.prototype.forEach&&t.forEach===Array.prototype.forEach)t.forEach(e,r);else if(t.length===+t.length){for(var a=0,n=t.length;n>a;a++)if(e.call(r,t[a],a,t)===breaker)return}else for(var o in t)if(e.call(r,t[o],o,t)===breaker)return;return t};return MG.clone=function(t){var e;if(null===t||"object"!=typeof t)return t;if(t instanceof Date)return e=new Date,e.setTime(t.getTime()),e;if(t instanceof Array){e=[];for(var r=0,a=t.length;a>r;r++)e[r]=MG.clone(t[r]);return e}if(t instanceof Object){e={};for(var n in t)t.hasOwnProperty(n)&&(e[n]=MG.clone(t[n]));return e}throw new Error("Unable to copy obj! Its type isn't supported.")},MG})}).call(this);
\ No newline at end of file