metrics-graphics-rails 2.1.3.2 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,16 @@
1
1
  !function() {
2
2
  var d3 = {
3
- version: "3.5.4"
3
+ version: "3.5.5"
4
4
  };
5
5
  var d3_arraySlice = [].slice, d3_array = function(list) {
6
6
  return d3_arraySlice.call(list);
7
7
  };
8
8
  var d3_document = this.document;
9
9
  function d3_documentElement(node) {
10
- return node && (node.ownerDocument || node.document).documentElement;
10
+ return node && (node.ownerDocument || node.document || node).documentElement;
11
11
  }
12
12
  function d3_window(node) {
13
- return node && node.ownerDocument ? node.ownerDocument.defaultView : node;
13
+ return node && (node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView);
14
14
  }
15
15
  if (d3_document) {
16
16
  try {
@@ -9,7 +9,7 @@
9
9
  root.MG = factory(root.d3, root.jQuery);
10
10
  }
11
11
  }(this, function(d3, $) {
12
- window.MG = {version: '2.1.0'};
12
+ window.MG = {version: '2.4.0'};
13
13
 
14
14
  var charts = {};
15
15
 
@@ -27,6 +27,7 @@
27
27
  var defaults = {};
28
28
  defaults.all = {
29
29
  missing_is_zero: false, // if true, missing values will be treated as zeros
30
+ missing_is_hidden: false, // if true, missing values will appear as broken segments
30
31
  legend: '' , // an array identifying the labels for a chart's lines
31
32
  legend_target: '', // if set, the specified element is populated with a legend
32
33
  error: '', // if set, a graph will show an error icon and log the error to the console
@@ -45,6 +46,7 @@
45
46
  small_text: false, // coerces small text regardless of graphic size
46
47
  xax_count: 6, // number of x axis ticks
47
48
  xax_tick_length: 5, // x axis tick length
49
+ xax_start_at_min: false,
48
50
  yax_count: 5, // number of y axis ticks
49
51
  yax_tick_length: 5, // y axis tick length
50
52
  x_extended_ticks: false, // extends x axis ticks across chart - useful for tall charts
@@ -84,9 +86,11 @@
84
86
  markers: null, // sets the marker lines
85
87
  scalefns: {},
86
88
  scales: {},
89
+ show_year_markers: false,
87
90
  show_secondary_x_label: true,
88
91
  target: '#viz',
89
92
  interpolate: 'cardinal', // interpolation method to use when rendering lines
93
+ interpolate_tension: 0.7, // its range is from 0 to 1; increase if your data is irregular and you notice artifacts
90
94
  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
91
95
  max_data_size: null, // explicitly specify the the max number of line series, for use with custom_line_color_map
92
96
  aggregate_rollover: false, // links the lines in a multi-line chart
@@ -148,6 +152,7 @@
148
152
  missing_text: 'Data currently missing or unavailable',
149
153
  scalefns: {},
150
154
  scales: {},
155
+ show_tooltips: true,
151
156
  show_missing_background: true,
152
157
  interpolate: 'cardinal'
153
158
  };
@@ -257,14 +262,14 @@
257
262
  this.enabled =
258
263
  this.timeout =
259
264
  this.hoverState =
260
- this.$element = null
265
+ this.$element = null;
261
266
 
262
- this.init('tooltip', element, options)
263
- }
267
+ this.init('tooltip', element, options);
268
+ };
264
269
 
265
- Tooltip.VERSION = '3.3.1'
270
+ Tooltip.VERSION = '3.3.1';
266
271
 
267
- Tooltip.TRANSITION_DURATION = 150
272
+ Tooltip.TRANSITION_DURATION = 150;
268
273
 
269
274
  Tooltip.DEFAULTS = {
270
275
  animation: true,
@@ -280,200 +285,200 @@
280
285
  selector: 'body',
281
286
  padding: 0
282
287
  }
283
- }
288
+ };
284
289
 
285
290
  Tooltip.prototype.init = function (type, element, options) {
286
- this.enabled = true
287
- this.type = type
288
- this.$element = $(element)
289
- this.options = this.getOptions(options)
290
- this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)
291
+ this.enabled = true;
292
+ this.type = type;
293
+ this.$element = $(element);
294
+ this.options = this.getOptions(options);
295
+ this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport);
291
296
 
292
- var triggers = this.options.trigger.split(' ')
297
+ var triggers = this.options.trigger.split(' ');
293
298
 
294
299
  for (var i = triggers.length; i--;) {
295
- var trigger = triggers[i]
300
+ var trigger = triggers[i];
296
301
 
297
302
  if (trigger == 'click') {
298
- this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
303
+ this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this));
299
304
  } else if (trigger != 'manual') {
300
- var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin'
301
- var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
305
+ var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin';
306
+ var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout';
302
307
 
303
- this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
304
- this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
308
+ this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this));
309
+ this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this));
305
310
  }
306
311
  }
307
312
 
308
313
  this.options.selector ?
309
314
  (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
310
- this.fixTitle()
311
- }
315
+ this.fixTitle();
316
+ };
312
317
 
313
318
  Tooltip.prototype.getDefaults = function () {
314
- return Tooltip.DEFAULTS
315
- }
319
+ return Tooltip.DEFAULTS;
320
+ };
316
321
 
317
322
  Tooltip.prototype.getOptions = function (options) {
318
- options = $.extend({}, this.getDefaults(), this.$element.data(), options)
323
+ options = $.extend({}, this.getDefaults(), this.$element.data(), options);
319
324
 
320
325
  if (options.delay && typeof options.delay == 'number') {
321
326
  options.delay = {
322
327
  show: options.delay,
323
328
  hide: options.delay
324
- }
329
+ };
325
330
  }
326
331
 
327
- return options
328
- }
332
+ return options;
333
+ };
329
334
 
330
335
  Tooltip.prototype.getDelegateOptions = function () {
331
- var options = {}
332
- var defaults = this.getDefaults()
336
+ var options = {};
337
+ var defaults = this.getDefaults();
333
338
 
334
339
  this._options && $.each(this._options, function (key, value) {
335
- if (defaults[key] != value) options[key] = value
336
- })
340
+ if (defaults[key] != value) options[key] = value;
341
+ });
337
342
 
338
- return options
339
- }
343
+ return options;
344
+ };
340
345
 
341
346
  Tooltip.prototype.enter = function (obj) {
342
347
  var self = obj instanceof this.constructor ?
343
- obj : $(obj.currentTarget).data('bs.' + this.type)
348
+ obj : $(obj.currentTarget).data('bs.' + this.type);
344
349
 
345
350
  if (self && self.$tip && self.$tip.is(':visible')) {
346
- self.hoverState = 'in'
347
- return
351
+ self.hoverState = 'in';
352
+ return;
348
353
  }
349
354
 
350
355
  if (!self) {
351
- self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
352
- $(obj.currentTarget).data('bs.' + this.type, self)
356
+ self = new this.constructor(obj.currentTarget, this.getDelegateOptions());
357
+ $(obj.currentTarget).data('bs.' + this.type, self);
353
358
  }
354
359
 
355
- clearTimeout(self.timeout)
360
+ clearTimeout(self.timeout);
356
361
 
357
- self.hoverState = 'in'
362
+ self.hoverState = 'in';
358
363
 
359
- if (!self.options.delay || !self.options.delay.show) return self.show()
364
+ if (!self.options.delay || !self.options.delay.show) return self.show();
360
365
 
361
366
  self.timeout = setTimeout(function () {
362
- if (self.hoverState == 'in') self.show()
363
- }, self.options.delay.show)
364
- }
367
+ if (self.hoverState == 'in') self.show();
368
+ }, self.options.delay.show);
369
+ };
365
370
 
366
371
  Tooltip.prototype.leave = function (obj) {
367
372
  var self = obj instanceof this.constructor ?
368
- obj : $(obj.currentTarget).data('bs.' + this.type)
373
+ obj : $(obj.currentTarget).data('bs.' + this.type);
369
374
 
370
375
  if (!self) {
371
- self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
372
- $(obj.currentTarget).data('bs.' + this.type, self)
376
+ self = new this.constructor(obj.currentTarget, this.getDelegateOptions());
377
+ $(obj.currentTarget).data('bs.' + this.type, self);
373
378
  }
374
379
 
375
- clearTimeout(self.timeout)
380
+ clearTimeout(self.timeout);
376
381
 
377
- self.hoverState = 'out'
382
+ self.hoverState = 'out';
378
383
 
379
- if (!self.options.delay || !self.options.delay.hide) return self.hide()
384
+ if (!self.options.delay || !self.options.delay.hide) return self.hide();
380
385
 
381
386
  self.timeout = setTimeout(function () {
382
- if (self.hoverState == 'out') self.hide()
383
- }, self.options.delay.hide)
384
- }
387
+ if (self.hoverState == 'out') self.hide();
388
+ }, self.options.delay.hide);
389
+ };
385
390
 
386
391
  Tooltip.prototype.show = function () {
387
- var e = $.Event('show.bs.' + this.type)
392
+ var e = $.Event('show.bs.' + this.type);
388
393
 
389
394
  if (this.hasContent() && this.enabled) {
390
- this.$element.trigger(e)
395
+ this.$element.trigger(e);
391
396
 
392
- var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
393
- if (e.isDefaultPrevented() || !inDom) return
394
- var that = this
397
+ var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]);
398
+ if (e.isDefaultPrevented() || !inDom) return;
399
+ var that = this;
395
400
 
396
- var $tip = this.tip()
401
+ var $tip = this.tip();
397
402
 
398
- var tipId = this.getUID(this.type)
403
+ var tipId = this.getUID(this.type);
399
404
 
400
- this.setContent()
401
- $tip.attr('id', tipId)
402
- this.$element.attr('aria-describedby', tipId)
405
+ this.setContent();
406
+ $tip.attr('id', tipId);
407
+ this.$element.attr('aria-describedby', tipId);
403
408
 
404
- if (this.options.animation) $tip.addClass('fade')
409
+ if (this.options.animation) $tip.addClass('fade');
405
410
 
406
411
  var placement = typeof this.options.placement == 'function' ?
407
412
  this.options.placement.call(this, $tip[0], this.$element[0]) :
408
- this.options.placement
413
+ this.options.placement;
409
414
 
410
- var autoToken = /\s?auto?\s?/i
411
- var autoPlace = autoToken.test(placement)
412
- if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
415
+ var autoToken = /\s?auto?\s?/i;
416
+ var autoPlace = autoToken.test(placement);
417
+ if (autoPlace) placement = placement.replace(autoToken, '') || 'top';
413
418
 
414
419
  $tip
415
420
  .detach()
416
421
  .css({ top: 0, left: 0, display: 'block' })
417
422
  .addClass(placement)
418
- .data('bs.' + this.type, this)
423
+ .data('bs.' + this.type, this);
419
424
 
420
- this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
425
+ this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element);
421
426
 
422
- var pos = this.getPosition()
423
- var actualWidth = $tip[0].offsetWidth
424
- var actualHeight = $tip[0].offsetHeight
427
+ var pos = this.getPosition();
428
+ var actualWidth = $tip[0].offsetWidth;
429
+ var actualHeight = $tip[0].offsetHeight;
425
430
 
426
431
  if (autoPlace) {
427
- var orgPlacement = placement
428
- var $container = this.options.container ? $(this.options.container) : this.$element.parent()
429
- var containerDim = this.getPosition($container)
432
+ var orgPlacement = placement;
433
+ var $container = this.options.container ? $(this.options.container) : this.$element.parent();
434
+ var containerDim = this.getPosition($container);
430
435
 
431
436
  placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top' :
432
437
  placement == 'top' && pos.top - actualHeight < containerDim.top ? 'bottom' :
433
438
  placement == 'right' && pos.right + actualWidth > containerDim.width ? 'left' :
434
439
  placement == 'left' && pos.left - actualWidth < containerDim.left ? 'right' :
435
- placement
440
+ placement;
436
441
 
437
442
  $tip
438
443
  .removeClass(orgPlacement)
439
- .addClass(placement)
444
+ .addClass(placement);
440
445
  }
441
446
 
442
- var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
447
+ var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight);
443
448
 
444
- this.applyPlacement(calculatedOffset, placement)
449
+ this.applyPlacement(calculatedOffset, placement);
445
450
 
446
451
  var complete = function () {
447
- var prevHoverState = that.hoverState
448
- that.$element.trigger('shown.bs.' + that.type)
449
- that.hoverState = null
452
+ var prevHoverState = that.hoverState;
453
+ that.$element.trigger('shown.bs.' + that.type);
454
+ that.hoverState = null;
450
455
 
451
- if (prevHoverState == 'out') that.leave(that)
452
- }
456
+ if (prevHoverState == 'out') that.leave(that);
457
+ };
453
458
 
454
459
  $.support.transition && this.$tip.hasClass('fade') ?
455
460
  $tip
456
461
  .one('bsTransitionEnd', complete)
457
462
  .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
458
- complete()
463
+ complete();
459
464
  }
460
- }
465
+ };
461
466
 
462
467
  Tooltip.prototype.applyPlacement = function (offset, placement) {
463
- var $tip = this.tip()
464
- var width = $tip[0].offsetWidth
465
- var height = $tip[0].offsetHeight
468
+ var $tip = this.tip();
469
+ var width = $tip[0].offsetWidth;
470
+ var height = $tip[0].offsetHeight;
466
471
 
467
472
  // manually read margins because getBoundingClientRect includes difference
468
- var marginTop = parseInt($tip.css('margin-top'), 10)
469
- var marginLeft = parseInt($tip.css('margin-left'), 10)
473
+ var marginTop = parseInt($tip.css('margin-top'), 10);
474
+ var marginLeft = parseInt($tip.css('margin-left'), 10);
470
475
 
471
476
  // we must check for NaN for ie 8/9
472
- if (isNaN(marginTop)) marginTop = 0
473
- if (isNaN(marginLeft)) marginLeft = 0
477
+ if (isNaN(marginTop)) marginTop = 0;
478
+ if (isNaN(marginLeft)) marginLeft = 0;
474
479
 
475
- offset.top = offset.top + marginTop
476
- offset.left = offset.left + marginLeft
480
+ offset.top = offset.top + marginTop;
481
+ offset.left = offset.left + marginLeft;
477
482
 
478
483
  // $.fn.offset doesn't round pixel values
479
484
  // so we use setOffset directly with our own function B-0
@@ -482,199 +487,199 @@
482
487
  $tip.css({
483
488
  top: Math.round(props.top),
484
489
  left: Math.round(props.left)
485
- })
490
+ });
486
491
  }
487
- }, offset), 0)
492
+ }, offset), 0);
488
493
 
489
- $tip.addClass('in')
494
+ $tip.addClass('in');
490
495
 
491
496
  // check to see if placing tip in new offset caused the tip to resize itself
492
- var actualWidth = $tip[0].offsetWidth
493
- var actualHeight = $tip[0].offsetHeight
497
+ var actualWidth = $tip[0].offsetWidth;
498
+ var actualHeight = $tip[0].offsetHeight;
494
499
 
495
500
  if (placement == 'top' && actualHeight != height) {
496
- offset.top = offset.top + height - actualHeight
501
+ offset.top = offset.top + height - actualHeight;
497
502
  }
498
503
 
499
- var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
504
+ var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight);
500
505
 
501
- if (delta.left) offset.left += delta.left
502
- else offset.top += delta.top
506
+ if (delta.left) offset.left += delta.left;
507
+ else offset.top += delta.top;
503
508
 
504
- var isVertical = /top|bottom/.test(placement)
505
- var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
506
- var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
509
+ var isVertical = /top|bottom/.test(placement);
510
+ var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight;
511
+ var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight';
507
512
 
508
- $tip.offset(offset)
509
- this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
510
- }
513
+ $tip.offset(offset);
514
+ this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical);
515
+ };
511
516
 
512
517
  Tooltip.prototype.replaceArrow = function (delta, dimension, isHorizontal) {
513
518
  this.arrow()
514
519
  .css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
515
- .css(isHorizontal ? 'top' : 'left', '')
516
- }
520
+ .css(isHorizontal ? 'top' : 'left', '');
521
+ };
517
522
 
518
523
  Tooltip.prototype.setContent = function () {
519
- var $tip = this.tip()
520
- var title = this.getTitle()
524
+ var $tip = this.tip();
525
+ var title = this.getTitle();
521
526
 
522
- $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
523
- $tip.removeClass('fade in top bottom left right')
524
- }
527
+ $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title);
528
+ $tip.removeClass('fade in top bottom left right');
529
+ };
525
530
 
526
531
  Tooltip.prototype.hide = function (callback) {
527
- var that = this
528
- var $tip = this.tip()
529
- var e = $.Event('hide.bs.' + this.type)
532
+ var that = this;
533
+ var $tip = this.tip();
534
+ var e = $.Event('hide.bs.' + this.type);
530
535
 
531
536
  function complete() {
532
- if (that.hoverState != 'in') $tip.detach()
537
+ if (that.hoverState != 'in') $tip.detach();
533
538
  that.$element
534
539
  .removeAttr('aria-describedby')
535
- .trigger('hidden.bs.' + that.type)
536
- callback && callback()
540
+ .trigger('hidden.bs.' + that.type);
541
+ callback && callback();
537
542
  }
538
543
 
539
- this.$element.trigger(e)
544
+ this.$element.trigger(e);
540
545
 
541
- if (e.isDefaultPrevented()) return
546
+ if (e.isDefaultPrevented()) return;
542
547
 
543
- $tip.removeClass('in')
548
+ $tip.removeClass('in');
544
549
 
545
550
  $.support.transition && this.$tip.hasClass('fade') ?
546
551
  $tip
547
552
  .one('bsTransitionEnd', complete)
548
553
  .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
549
- complete()
554
+ complete();
550
555
 
551
- this.hoverState = null
556
+ this.hoverState = null;
552
557
 
553
- return this
554
- }
558
+ return this;
559
+ };
555
560
 
556
561
  Tooltip.prototype.fixTitle = function () {
557
- var $e = this.$element
562
+ var $e = this.$element;
558
563
  if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {
559
- $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
564
+ $e.attr('data-original-title', $e.attr('title') || '').attr('title', '');
560
565
  }
561
- }
566
+ };
562
567
 
563
568
  Tooltip.prototype.hasContent = function () {
564
- return this.getTitle()
565
- }
569
+ return this.getTitle();
570
+ };
566
571
 
567
572
  Tooltip.prototype.getPosition = function ($element) {
568
- $element = $element || this.$element
573
+ $element = $element || this.$element;
569
574
 
570
- var el = $element[0]
571
- var isBody = el.tagName == 'BODY'
575
+ var el = $element[0];
576
+ var isBody = el.tagName == 'BODY';
572
577
 
573
- var elRect = el.getBoundingClientRect()
578
+ var elRect = el.getBoundingClientRect();
574
579
  if (elRect.width == null) {
575
580
  // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
576
- elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
581
+ elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top });
577
582
  }
578
- var elOffset = isBody ? { top: 0, left: 0 } : $element.offset()
579
- var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
580
- var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
583
+ var elOffset = isBody ? { top: 0, left: 0 } : $element.offset();
584
+ var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() };
585
+ var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null;
581
586
 
582
- return $.extend({}, elRect, scroll, outerDims, elOffset)
583
- }
587
+ return $.extend({}, elRect, scroll, outerDims, elOffset);
588
+ };
584
589
 
585
590
  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
586
591
  return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
587
592
  placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
588
593
  placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
589
- /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
594
+ /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width };
590
595
 
591
- }
596
+ };
592
597
 
593
598
  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
594
- var delta = { top: 0, left: 0 }
595
- if (!this.$viewport) return delta
599
+ var delta = { top: 0, left: 0 };
600
+ if (!this.$viewport) return delta;
596
601
 
597
- var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
598
- var viewportDimensions = this.getPosition(this.$viewport)
602
+ var viewportPadding = this.options.viewport && this.options.viewport.padding || 0;
603
+ var viewportDimensions = this.getPosition(this.$viewport);
599
604
 
600
605
  if (/right|left/.test(placement)) {
601
- var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
602
- var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
606
+ var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll;
607
+ var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight;
603
608
  if (topEdgeOffset < viewportDimensions.top) { // top overflow
604
- delta.top = viewportDimensions.top - topEdgeOffset
609
+ delta.top = viewportDimensions.top - topEdgeOffset;
605
610
  } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
606
- delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
611
+ delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset;
607
612
  }
608
613
  } else {
609
- var leftEdgeOffset = pos.left - viewportPadding
610
- var rightEdgeOffset = pos.left + viewportPadding + actualWidth
614
+ var leftEdgeOffset = pos.left - viewportPadding;
615
+ var rightEdgeOffset = pos.left + viewportPadding + actualWidth;
611
616
  if (leftEdgeOffset < viewportDimensions.left) { // left overflow
612
- delta.left = viewportDimensions.left - leftEdgeOffset
617
+ delta.left = viewportDimensions.left - leftEdgeOffset;
613
618
  } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow
614
- delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
619
+ delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset;
615
620
  }
616
621
  }
617
622
 
618
- return delta
619
- }
623
+ return delta;
624
+ };
620
625
 
621
626
  Tooltip.prototype.getTitle = function () {
622
- var title
623
- var $e = this.$element
624
- var o = this.options
627
+ var title;
628
+ var $e = this.$element;
629
+ var o = this.options;
625
630
 
626
631
  title = $e.attr('data-original-title')
627
- || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
632
+ || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title);
628
633
 
629
- return title
630
- }
634
+ return title;
635
+ };
631
636
 
632
637
  Tooltip.prototype.getUID = function (prefix) {
633
- do prefix += ~~(Math.random() * 1000000)
634
- while (document.getElementById(prefix))
635
- return prefix
636
- }
638
+ do prefix += ~~(Math.random() * 1000000);
639
+ while (document.getElementById(prefix));
640
+ return prefix;
641
+ };
637
642
 
638
643
  Tooltip.prototype.tip = function () {
639
- return (this.$tip = this.$tip || $(this.options.template))
640
- }
644
+ return (this.$tip = this.$tip || $(this.options.template));
645
+ };
641
646
 
642
647
  Tooltip.prototype.arrow = function () {
643
- return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
644
- }
648
+ return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'));
649
+ };
645
650
 
646
651
  Tooltip.prototype.enable = function () {
647
- this.enabled = true
648
- }
652
+ this.enabled = true;
653
+ };
649
654
 
650
655
  Tooltip.prototype.disable = function () {
651
- this.enabled = false
652
- }
656
+ this.enabled = false;
657
+ };
653
658
 
654
659
  Tooltip.prototype.toggleEnabled = function () {
655
- this.enabled = !this.enabled
656
- }
660
+ this.enabled = !this.enabled;
661
+ };
657
662
 
658
663
  Tooltip.prototype.toggle = function (e) {
659
- var self = this
664
+ var self = this;
660
665
  if (e) {
661
- self = $(e.currentTarget).data('bs.' + this.type)
666
+ self = $(e.currentTarget).data('bs.' + this.type);
662
667
  if (!self) {
663
- self = new this.constructor(e.currentTarget, this.getDelegateOptions())
664
- $(e.currentTarget).data('bs.' + this.type, self)
668
+ self = new this.constructor(e.currentTarget, this.getDelegateOptions());
669
+ $(e.currentTarget).data('bs.' + this.type, self);
665
670
  }
666
671
  }
667
672
 
668
- self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
669
- }
673
+ self.tip().hasClass('in') ? self.leave(self) : self.enter(self);
674
+ };
670
675
 
671
676
  Tooltip.prototype.destroy = function () {
672
- var that = this
673
- clearTimeout(this.timeout)
677
+ var that = this;
678
+ clearTimeout(this.timeout);
674
679
  this.hide(function () {
675
- that.$element.off('.' + that.type).removeData('bs.' + that.type)
676
- })
677
- }
680
+ that.$element.off('.' + that.type).removeData('bs.' + that.type);
681
+ });
682
+ };
678
683
 
679
684
 
680
685
  // TOOLTIP PLUGIN DEFINITION
@@ -682,35 +687,35 @@
682
687
 
683
688
  function Plugin(option) {
684
689
  return this.each(function () {
685
- var $this = $(this)
686
- var data = $this.data('bs.tooltip')
687
- var options = typeof option == 'object' && option
688
- var selector = options && options.selector
690
+ var $this = $(this);
691
+ var data = $this.data('bs.tooltip');
692
+ var options = typeof option == 'object' && option;
693
+ var selector = options && options.selector;
689
694
 
690
- if (!data && option == 'destroy') return
695
+ if (!data && option == 'destroy') return;
691
696
  if (selector) {
692
- if (!data) $this.data('bs.tooltip', (data = {}))
693
- if (!data[selector]) data[selector] = new Tooltip(this, options)
697
+ if (!data) $this.data('bs.tooltip', (data = {}));
698
+ if (!data[selector]) data[selector] = new Tooltip(this, options);
694
699
  } else {
695
- if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
700
+ if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)));
696
701
  }
697
- if (typeof option == 'string') data[option]()
698
- })
702
+ if (typeof option == 'string') data[option]();
703
+ });
699
704
  }
700
705
 
701
- var old = $.fn.tooltip
706
+ var old = $.fn.tooltip;
702
707
 
703
- $.fn.tooltip = Plugin
704
- $.fn.tooltip.Constructor = Tooltip
708
+ $.fn.tooltip = Plugin;
709
+ $.fn.tooltip.Constructor = Tooltip;
705
710
 
706
711
 
707
712
  // TOOLTIP NO CONFLICT
708
713
  // ===================
709
714
 
710
715
  $.fn.tooltip.noConflict = function () {
711
- $.fn.tooltip = old
712
- return this
713
- }
716
+ $.fn.tooltip = old;
717
+ return this;
718
+ };
714
719
 
715
720
  }(jQuery);
716
721
 
@@ -733,71 +738,71 @@
733
738
  // ===============================
734
739
 
735
740
  var Popover = function (element, options) {
736
- this.init('popover', element, options)
737
- }
741
+ this.init('popover', element, options);
742
+ };
738
743
 
739
- if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
744
+ if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js');
740
745
 
741
- Popover.VERSION = '3.3.1'
746
+ Popover.VERSION = '3.3.1';
742
747
 
743
748
  Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
744
749
  placement: 'right',
745
750
  trigger: 'click',
746
751
  content: '',
747
752
  template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
748
- })
753
+ });
749
754
 
750
755
 
751
756
  // NOTE: POPOVER EXTENDS tooltip.js
752
757
  // ================================
753
758
 
754
- Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
759
+ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype);
755
760
 
756
- Popover.prototype.constructor = Popover
761
+ Popover.prototype.constructor = Popover;
757
762
 
758
763
  Popover.prototype.getDefaults = function () {
759
- return Popover.DEFAULTS
760
- }
764
+ return Popover.DEFAULTS;
765
+ };
761
766
 
762
767
  Popover.prototype.setContent = function () {
763
- var $tip = this.tip()
764
- var title = this.getTitle()
765
- var content = this.getContent()
768
+ var $tip = this.tip();
769
+ var title = this.getTitle();
770
+ var content = this.getContent();
766
771
 
767
- $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
772
+ $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title);
768
773
  $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
769
774
  this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
770
- ](content)
775
+ ](content);
771
776
 
772
- $tip.removeClass('fade top bottom left right in')
777
+ $tip.removeClass('fade top bottom left right in');
773
778
 
774
779
  // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
775
780
  // this manually by checking the contents.
776
- if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
777
- }
781
+ if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide();
782
+ };
778
783
 
779
784
  Popover.prototype.hasContent = function () {
780
- return this.getTitle() || this.getContent()
781
- }
785
+ return this.getTitle() || this.getContent();
786
+ };
782
787
 
783
788
  Popover.prototype.getContent = function () {
784
- var $e = this.$element
785
- var o = this.options
789
+ var $e = this.$element;
790
+ var o = this.options;
786
791
 
787
792
  return $e.attr('data-content')
788
793
  || (typeof o.content == 'function' ?
789
794
  o.content.call($e[0]) :
790
- o.content)
791
- }
795
+ o.content);
796
+ };
792
797
 
793
798
  Popover.prototype.arrow = function () {
794
- return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
795
- }
799
+ return (this.$arrow = this.$arrow || this.tip().find('.arrow'));
800
+ };
796
801
 
797
802
  Popover.prototype.tip = function () {
798
- if (!this.$tip) this.$tip = $(this.options.template)
799
- return this.$tip
800
- }
803
+ if (!this.$tip) this.$tip = $(this.options.template);
804
+ return this.$tip;
805
+ };
801
806
 
802
807
 
803
808
  // POPOVER PLUGIN DEFINITION
@@ -805,35 +810,35 @@
805
810
 
806
811
  function Plugin(option) {
807
812
  return this.each(function () {
808
- var $this = $(this)
809
- var data = $this.data('bs.popover')
810
- var options = typeof option == 'object' && option
811
- var selector = options && options.selector
813
+ var $this = $(this);
814
+ var data = $this.data('bs.popover');
815
+ var options = typeof option == 'object' && option;
816
+ var selector = options && options.selector;
812
817
 
813
- if (!data && option == 'destroy') return
818
+ if (!data && option == 'destroy') return;
814
819
  if (selector) {
815
- if (!data) $this.data('bs.popover', (data = {}))
816
- if (!data[selector]) data[selector] = new Popover(this, options)
820
+ if (!data) $this.data('bs.popover', (data = {}));
821
+ if (!data[selector]) data[selector] = new Popover(this, options);
817
822
  } else {
818
- if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
823
+ if (!data) $this.data('bs.popover', (data = new Popover(this, options)));
819
824
  }
820
- if (typeof option == 'string') data[option]()
821
- })
825
+ if (typeof option == 'string') data[option]();
826
+ });
822
827
  }
823
828
 
824
- var old = $.fn.popover
829
+ var old = $.fn.popover;
825
830
 
826
- $.fn.popover = Plugin
827
- $.fn.popover.Constructor = Popover
831
+ $.fn.popover = Plugin;
832
+ $.fn.popover.Constructor = Popover;
828
833
 
829
834
 
830
835
  // POPOVER NO CONFLICT
831
836
  // ===================
832
837
 
833
838
  $.fn.popover.noConflict = function () {
834
- $.fn.popover = old
835
- return this
836
- }
839
+ $.fn.popover = old;
840
+ return this;
841
+ };
837
842
 
838
843
  }(jQuery);
839
844
  }
@@ -878,7 +883,7 @@
878
883
  function y_rug(args) {
879
884
  'use strict';
880
885
  var svg = mg_get_svg_child_of(args.target);
881
-
886
+
882
887
  var buffer_size = args.chart_type === 'point'
883
888
  ? args.buffer / 2
884
889
  : args.buffer * 2 / 3;
@@ -931,6 +936,11 @@
931
936
  max_y;
932
937
 
933
938
  args.scalefns.yf = function(di) {
939
+ //since we want to show actual zeros when missing_is_hidden is on
940
+ if(args.missing_is_hidden && di['missing']) {
941
+ return args.scales.Y(di[args.y_accessor]) + 42.1234;
942
+ }
943
+
934
944
  return args.scales.Y(di[args.y_accessor]);
935
945
  };
936
946
 
@@ -1040,7 +1050,7 @@
1040
1050
  };
1041
1051
  } else { //percentage
1042
1052
  yax_format = function(d_) {
1043
- var n = d3.format('%p');
1053
+ var n = d3.format('2p');
1044
1054
  return n(d_);
1045
1055
  };
1046
1056
  }
@@ -1205,16 +1215,15 @@
1205
1215
  var buffer_size = args.chart_type === 'point'
1206
1216
  ? args.buffer / 2
1207
1217
  : args.buffer;
1208
-
1209
1218
  var svg = mg_get_svg_child_of(args.target);
1210
1219
 
1211
- var all_data=[];
1212
- for (var i=0; i<args.data.length; i++) {
1213
- for (var j=0; j<args.data[i].length; j++) {
1214
- all_data.push(args.data[i][j]);
1215
- }
1216
- }
1217
-
1220
+ var all_data = mg_flatten_array(args.data)
1221
+ // for (var i=0; i<args.data.length; i++) {
1222
+ // for (var j=0; j<args.data[i].length; j++) {
1223
+ // all_data.push(args.data[i][j]);
1224
+ // }
1225
+ // }
1226
+
1218
1227
  var rug = svg.selectAll('line.mg-x-rug').data(all_data);
1219
1228
 
1220
1229
  //set the attributes that do not change after initialization, per
@@ -1251,7 +1260,12 @@
1251
1260
  var max_x;
1252
1261
 
1253
1262
  args.processed = {};
1254
-
1263
+ var all_data = [];
1264
+ for (var i = 0; i < args.data.length; i++) {
1265
+ for (var j = 0; j < args.data[i].length; j++) {
1266
+ all_data.push(args.data[i][j]);
1267
+ }
1268
+ }
1255
1269
  args.scalefns.xf = function(di) {
1256
1270
  return args.scales.X(di[args.x_accessor]);
1257
1271
  };
@@ -1266,6 +1280,7 @@
1266
1280
  args.scales.X = (args.time_series)
1267
1281
  ? d3.time.scale()
1268
1282
  : d3.scale.linear();
1283
+
1269
1284
  args.scales.X
1270
1285
  .domain([args.processed.min_x, args.processed.max_x])
1271
1286
  .range([args.left + args.buffer, args.width - args.right - args.buffer - args.additional_buffer]);
@@ -1445,9 +1460,7 @@
1445
1460
  g.append('text')
1446
1461
  .attr('class', 'label')
1447
1462
  .attr('x', function() {
1448
- return args.left + args.buffer
1449
- + ((args.width - args.right - args.buffer)
1450
- - (args.left + args.buffer)) / 2;
1463
+ return (args.left + args.width - args.right) / 2;
1451
1464
  })
1452
1465
  .attr('y', (args.height - args.bottom / 2).toFixed(2))
1453
1466
  .attr('dy', '.50em')
@@ -1477,41 +1490,42 @@
1477
1490
  if (args.xax_format) {
1478
1491
  return args.xax_format;
1479
1492
  }
1493
+ var test_point = mg_flatten_array(args.data)[0][args.x_accessor]
1480
1494
 
1481
- var diff,
1482
- main_time_format,
1483
- time_frame;
1484
-
1485
- if (args.time_series) {
1486
- diff = (args.processed.max_x - args.processed.min_x) / 1000;
1487
-
1488
- if (diff < 60) {
1489
- main_time_format = d3.time.format('%M:%S');
1490
- time_frame = 'seconds';
1491
- } else if (diff / (60 * 60) <= 24) {
1492
- main_time_format = d3.time.format('%H:%M');
1493
- time_frame = 'less-than-a-day';
1494
- } else if (diff / (60 * 60) <= 24 * 4) {
1495
- main_time_format = d3.time.format('%H:%M');
1496
- time_frame = 'four-days';
1497
- } else {
1498
- main_time_format = d3.time.format('%b %d');
1499
- time_frame = 'default';
1495
+ return function(d) {
1496
+ var diff;
1497
+ var main_time_format;
1498
+ var time_frame;
1499
+
1500
+ if (args.time_series) {
1501
+ diff = (args.processed.max_x - args.processed.min_x) / 1000;
1502
+
1503
+ if (diff < 60) {
1504
+ main_time_format = d3.time.format('%M:%S');
1505
+ time_frame = 'seconds';
1506
+ } else if (diff / (60 * 60) <= 24) {
1507
+ main_time_format = d3.time.format('%H:%M');
1508
+ time_frame = 'less-than-a-day';
1509
+ } else if (diff / (60 * 60) <= 24 * 4) {
1510
+ main_time_format = d3.time.format('%H:%M');
1511
+ time_frame = 'four-days';
1512
+ } else {
1513
+ main_time_format = d3.time.format('%b %d');
1514
+ time_frame = 'default';
1515
+ }
1500
1516
  }
1501
- }
1502
1517
 
1503
- args.processed.main_x_time_format = main_time_format;
1504
- args.processed.x_time_frame = time_frame;
1518
+ args.processed.main_x_time_format = main_time_format;
1519
+ args.processed.x_time_frame = time_frame;
1505
1520
 
1506
- return function(d) {
1507
1521
  var df = d3.time.format('%b %d');
1508
1522
  var pf = d3.formatPrefix(d);
1509
1523
 
1510
1524
  // format as date or not, of course user can pass in
1511
1525
  // a custom function if desired
1512
- if(args.data[0][0][args.x_accessor] instanceof Date) {
1513
- return args.processed.main_x_time_format(d);
1514
- } else if($.type(args.data[0][0][args.x_accessor]) === 'number') {
1526
+ if(test_point instanceof Date) {
1527
+ return args.processed.main_x_time_format(new Date(d));
1528
+ } else if (typeof test_point === 'number') {
1515
1529
  if (d < 1.0) {
1516
1530
  //don't scale tiny values
1517
1531
  return args.xax_units + d3.round(d, args.decimals);
@@ -1527,17 +1541,32 @@
1527
1541
 
1528
1542
  function mg_add_x_ticks(g, args) {
1529
1543
  var last_i = args.scales.X.ticks(args.xax_count).length - 1;
1544
+ var ticks = args.scales.X.ticks(args.xax_count);
1545
+
1546
+ //force min to be the first tick rather than the first element in ticks
1547
+ if(args.xax_start_at_min) {
1548
+ ticks[0] = args.processed.min_x;
1549
+ }
1530
1550
 
1531
1551
  if (args.chart_type !== 'bar' && !args.x_extended_ticks && !args.y_extended_ticks) {
1532
1552
  //extend axis line across bottom, rather than from domain's min..max
1533
1553
  g.append('line')
1534
- .attr('x1',
1535
- (args.concise === false || args.xax_count === 0)
1536
- ? args.left + args.buffer
1537
- : (args.scales.X(args.scales.X.ticks(args.xax_count)[0])).toFixed(2)
1538
- )
1554
+ .attr('x1', function() {
1555
+ //start the axis line from the beginning, domain's min, or the auto-generated
1556
+ //ticks' first element, depending on whether xax_count is set to 0 or
1557
+ //xax_start_at_min is set to true
1558
+ if (args.xax_count === 0) {
1559
+ return args.left + args.buffer;
1560
+ }
1561
+ else if (args.xax_start_at_min) {
1562
+ return args.scales.X(args.processed.min_x).toFixed(2)
1563
+ }
1564
+ else {
1565
+ return (args.scales.X(args.scales.X.ticks(args.xax_count)[0])).toFixed(2);
1566
+ }
1567
+ })
1539
1568
  .attr('x2',
1540
- (args.concise === false || args.xax_count === 0)
1569
+ (args.xax_count === 0)
1541
1570
  ? args.width - args.right - args.buffer
1542
1571
  : (args.scales.X(args.scales.X.ticks(args.xax_count)[last_i])).toFixed(2)
1543
1572
  )
@@ -1546,7 +1575,7 @@
1546
1575
  }
1547
1576
 
1548
1577
  g.selectAll('.mg-xax-ticks')
1549
- .data(args.scales.X.ticks(args.xax_count)).enter()
1578
+ .data(ticks).enter()
1550
1579
  .append('line')
1551
1580
  .attr('x1', function(d) { return args.scales.X(d).toFixed(2); })
1552
1581
  .attr('x2', function(d) { return args.scales.X(d).toFixed(2); })
@@ -1564,8 +1593,15 @@
1564
1593
  }
1565
1594
 
1566
1595
  function mg_add_x_tick_labels(g, args) {
1596
+ var ticks = args.scales.X.ticks(args.xax_count);
1597
+
1598
+ //force min to be the first tick rather than the first element in ticks
1599
+ if(args.xax_start_at_min) {
1600
+ ticks[0] = args.processed.min_x;
1601
+ }
1602
+
1567
1603
  g.selectAll('.mg-xax-labels')
1568
- .data(args.scales.X.ticks(args.xax_count)).enter()
1604
+ .data(ticks).enter()
1569
1605
  .append('text')
1570
1606
  .attr('x', function(d) { return args.scales.X(d).toFixed(2); })
1571
1607
  .attr('y', (args.height - args.bottom + args.xax_tick_length * 7 / 3).toFixed(2))
@@ -1601,7 +1637,11 @@
1601
1637
 
1602
1638
  var years = secondary_function(args.processed.min_x, args.processed.max_x);
1603
1639
 
1604
- if (years.length === 0) {
1640
+ //if xax_start_at_min is set
1641
+ if (args.xax_start_at_min && years.length === 0) {
1642
+ var first_tick = ticks[0];
1643
+ years = [first_tick];
1644
+ } else if (years.length === 0) {
1605
1645
  var first_tick = args.scales.X.ticks(args.xax_count)[0];
1606
1646
  years = [first_tick];
1607
1647
  }
@@ -1611,7 +1651,7 @@
1611
1651
  .classed('mg-year-marker', true)
1612
1652
  .classed('mg-year-marker-small', args.use_small_class);
1613
1653
 
1614
- if (time_frame === 'default') {
1654
+ if (time_frame === 'default' && args.show_year_markers) {
1615
1655
  g.selectAll('.mg-year-marker')
1616
1656
  .data(years).enter()
1617
1657
  .append('line')
@@ -1624,12 +1664,18 @@
1624
1664
  g.selectAll('.mg-year-marker')
1625
1665
  .data(years).enter()
1626
1666
  .append('text')
1627
- .attr('x', function(d) { return args.scales.X(d).toFixed(2); })
1667
+ .attr('x', function(d, i) {
1668
+ if (args.xax_start_at_min && i == 0) {
1669
+ d = ticks[0];
1670
+ }
1671
+
1672
+ return args.scales.X(d).toFixed(2);
1673
+ })
1628
1674
  .attr('y', (args.height - args.bottom + args.xax_tick_length * 7 / 1.3).toFixed(2))
1629
- .attr('dy', args.use_small_class ? -3 : 0)//(args.y_extended_ticks) ? 0 : 0 )
1675
+ .attr('dy', args.use_small_class ? -3 : 0)
1630
1676
  .attr('text-anchor', 'middle')
1631
1677
  .text(function(d) {
1632
- return yformat(d);
1678
+ return yformat(new Date(d));
1633
1679
  });
1634
1680
  }
1635
1681
  }
@@ -1643,7 +1689,9 @@
1643
1689
  mapDtoX = function(d) { return d[args.x_accessor]; };
1644
1690
 
1645
1691
  // clear the cached xax_format in case we need to recalculate
1646
- delete args.xax_format;
1692
+ if(args.xax_format === null) {
1693
+ delete args.xax_format;
1694
+ }
1647
1695
 
1648
1696
  if (args.chart_type === 'line' || args.chart_type === 'point' || args.chart_type === 'histogram') {
1649
1697
  extent_x = d3.extent(all_data, mapDtoX);
@@ -1662,10 +1710,9 @@
1662
1710
  });
1663
1711
  }
1664
1712
  //if data set is of length 1, expand the range so that we can build the x-axis
1665
- //of course, a line chart doesn't make sense in this case, so the preferred
1666
- //method would be to check for said object's length and, if appropriate,
1667
- //change the chart type to 'point'
1668
- if (min_x === max_x) {
1713
+ if (min_x === max_x
1714
+ && !(args.min_x && args.max_x)
1715
+ ) {
1669
1716
  if (min_x instanceof Date) {
1670
1717
  var yesterday = MG.clone(min_x).setDate(min_x.getDate() - 1);
1671
1718
  var tomorrow = MG.clone(min_x).setDate(min_x.getDate() + 1);
@@ -1722,7 +1769,10 @@
1722
1769
  description: null
1723
1770
  };
1724
1771
 
1725
- var args = arguments[0];
1772
+ // If you pass in a dom element for args.target, the expectation
1773
+ // of a string elsewhere will break.
1774
+
1775
+ args = arguments[0];
1726
1776
  if (!args) { args = {}; }
1727
1777
  args = merge_with_defaults(args, defaults);
1728
1778
 
@@ -1738,12 +1788,15 @@
1738
1788
  //but with the intention of using multiple values for multilines, etc.
1739
1789
 
1740
1790
  //do we have a time_series?
1741
- if (args.data[0][0][args.x_accessor] instanceof Date) {
1742
- args.time_series = true;
1743
- } else {
1744
- args.time_series = false;
1791
+
1792
+ function is_time_series(args) {
1793
+ var flat_data = [];
1794
+ var first_elem = mg_flatten_array(args.data)[0];
1795
+ return first_elem[args.x_accessor] instanceof Date;
1745
1796
  }
1746
1797
 
1798
+ args.time_series = is_time_series(args);
1799
+
1747
1800
  var svg_width = args.width;
1748
1801
  var svg_height = args.height;
1749
1802
 
@@ -1753,7 +1806,7 @@
1753
1806
  svg_width = get_width(args.target);
1754
1807
  }
1755
1808
 
1756
- if (args.fill_height) {
1809
+ if (args.full_height) {
1757
1810
  svg_height = get_height(args.target);
1758
1811
  }
1759
1812
 
@@ -1790,7 +1843,7 @@
1790
1843
  svg.append('defs')
1791
1844
  .attr('class', 'mg-clip-path')
1792
1845
  .append('clipPath')
1793
- .attr('class', 'mg-plot-window-' + mg_strip_punctuation(args.target))
1846
+ .attr('id', 'mg-plot-window-' + mg_strip_punctuation(args.target))
1794
1847
  .append('svg:rect')
1795
1848
  .attr('x', args.left)
1796
1849
  .attr('y', args.top)
@@ -1806,7 +1859,7 @@
1806
1859
  svg.attr('height', svg_height);
1807
1860
  }
1808
1861
 
1809
- // This is an unfinished feature. Need to reconsider how we handle automatic scaling.
1862
+ // @todo need to reconsider how we handle automatic scaling
1810
1863
  svg.attr('viewBox', '0 0 ' + svg_width + ' ' + svg_height);
1811
1864
 
1812
1865
  if (args.full_width || args.full_height) {
@@ -1815,7 +1868,7 @@
1815
1868
 
1816
1869
  // remove missing class
1817
1870
  svg.classed('mg-missing', false);
1818
-
1871
+
1819
1872
  // remove missing text
1820
1873
  svg.selectAll('.mg-missing-text').remove();
1821
1874
  svg.selectAll('.mg-missing-pane').remove();
@@ -1833,7 +1886,7 @@
1833
1886
  //data_graphic() on the same target with 2 lines, remove the 3rd line
1834
1887
 
1835
1888
  var i = 0;
1836
- if (args.data.length < svg.selectAll('.mg-main-line')[0].length) {
1889
+ if (svg.selectAll('.mg-main-line')[0].length >= args.data.length) {
1837
1890
  //now, the thing is we can't just remove, say, line3 if we have a custom
1838
1891
  //line-color map, instead, see which are the lines to be removed, and delete those
1839
1892
  if (args.custom_line_color_map.length > 0) {
@@ -1868,6 +1921,8 @@
1868
1921
  return this;
1869
1922
  }
1870
1923
 
1924
+
1925
+
1871
1926
  function markers(args) {
1872
1927
  'use strict';
1873
1928
  var svg = mg_get_svg_child_of(args.target);
@@ -1883,18 +1938,11 @@
1883
1938
  .attr('class', 'mg-markers');
1884
1939
 
1885
1940
  gm.selectAll('.mg-markers')
1886
- .data(args.markers.filter(function(d){
1887
- return (args.scales.X(d[args.x_accessor]) > args.buffer + args.left)
1888
- && (args.scales.X(d[args.x_accessor]) < args.width - args.buffer - args.right);
1889
- }))
1941
+ .data(args.markers.filter(inRange))
1890
1942
  .enter()
1891
1943
  .append('line')
1892
- .attr('x1', function(d) {
1893
- return args.scales.X(d[args.x_accessor]).toFixed(2);
1894
- })
1895
- .attr('x2', function(d) {
1896
- return args.scales.X(d[args.x_accessor]).toFixed(2);
1897
- })
1944
+ .attr('x1', xPositionFixed)
1945
+ .attr('x2', xPositionFixed)
1898
1946
  .attr('y1', args.top)
1899
1947
  .attr('y2', function() {
1900
1948
  return args.height - args.bottom - args.buffer;
@@ -1902,20 +1950,32 @@
1902
1950
  .attr('stroke-dasharray', '3,1');
1903
1951
 
1904
1952
  gm.selectAll('.mg-markers')
1905
- .data(args.markers.filter(function(d){
1906
- return (args.scales.X(d[args.x_accessor]) > args.buffer + args.left)
1907
- && (args.scales.X(d[args.x_accessor]) < args.width - args.buffer - args.right);
1908
- }))
1953
+ .data(args.markers.filter(inRange))
1909
1954
  .enter()
1910
1955
  .append('text')
1911
- .attr('x', function(d) {
1912
- return args.scales.X(d[args.x_accessor]);
1913
- })
1956
+ .attr('class', 'mg-marker-text')
1957
+ .attr('x', xPosition)
1914
1958
  .attr('y', args.top - 8)
1915
1959
  .attr('text-anchor', 'middle')
1916
1960
  .text(function(d) {
1917
1961
  return d.label;
1918
1962
  });
1963
+
1964
+ preventHorizontalOverlap(gm.selectAll('.mg-marker-text')[0], args);
1965
+ }
1966
+
1967
+
1968
+ function xPosition(d) {
1969
+ return args.scales.X(d[args.x_accessor]);
1970
+ }
1971
+
1972
+ function xPositionFixed(d) {
1973
+ return xPosition(d).toFixed(2);
1974
+ }
1975
+
1976
+ function inRange(d) {
1977
+ return (args.scales.X(d[args.x_accessor]) > args.buffer + args.left)
1978
+ && (args.scales.X(d[args.x_accessor]) < args.width - args.buffer - args.right);
1919
1979
  }
1920
1980
 
1921
1981
  if (args.baselines) {
@@ -1999,109 +2059,109 @@
1999
2059
  // DROPDOWN CLASS DEFINITION
2000
2060
  // =========================
2001
2061
 
2002
- var backdrop = '.dropdown-backdrop'
2003
- var toggle = '[data-toggle="dropdown"]'
2062
+ var backdrop = '.dropdown-backdrop';
2063
+ var toggle = '[data-toggle="dropdown"]';
2004
2064
  var Dropdown = function (element) {
2005
- $(element).on('click.bs.dropdown', this.toggle)
2006
- }
2065
+ $(element).on('click.bs.dropdown', this.toggle);
2066
+ };
2007
2067
 
2008
- Dropdown.VERSION = '3.3.1'
2068
+ Dropdown.VERSION = '3.3.1';
2009
2069
 
2010
2070
  Dropdown.prototype.toggle = function (e) {
2011
- var $this = $(this)
2071
+ var $this = $(this);
2012
2072
 
2013
- if ($this.is('.disabled, :disabled')) return
2073
+ if ($this.is('.disabled, :disabled')) return;
2014
2074
 
2015
- var $parent = getParent($this)
2016
- var isActive = $parent.hasClass('open')
2075
+ var $parent = getParent($this);
2076
+ var isActive = $parent.hasClass('open');
2017
2077
 
2018
- clearMenus()
2078
+ clearMenus();
2019
2079
 
2020
2080
  if (!isActive) {
2021
2081
  if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
2022
2082
  // if mobile we use a backdrop because click events don't delegate
2023
- $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus)
2083
+ $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus);
2024
2084
  }
2025
2085
 
2026
- var relatedTarget = { relatedTarget: this }
2027
- $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
2086
+ var relatedTarget = { relatedTarget: this };
2087
+ $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget));
2028
2088
 
2029
- if (e.isDefaultPrevented()) return
2089
+ if (e.isDefaultPrevented()) return;
2030
2090
 
2031
2091
  $this
2032
2092
  .trigger('focus')
2033
- .attr('aria-expanded', 'true')
2093
+ .attr('aria-expanded', 'true');
2034
2094
 
2035
2095
  $parent
2036
2096
  .toggleClass('open')
2037
- .trigger('shown.bs.dropdown', relatedTarget)
2097
+ .trigger('shown.bs.dropdown', relatedTarget);
2038
2098
  }
2039
2099
 
2040
- return false
2041
- }
2100
+ return false;
2101
+ };
2042
2102
 
2043
2103
  Dropdown.prototype.keydown = function (e) {
2044
- if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
2104
+ if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return;
2045
2105
 
2046
- var $this = $(this)
2106
+ var $this = $(this);
2047
2107
 
2048
- e.preventDefault()
2049
- e.stopPropagation()
2108
+ e.preventDefault();
2109
+ e.stopPropagation();
2050
2110
 
2051
- if ($this.is('.disabled, :disabled')) return
2111
+ if ($this.is('.disabled, :disabled')) return;
2052
2112
 
2053
- var $parent = getParent($this)
2054
- var isActive = $parent.hasClass('open')
2113
+ var $parent = getParent($this);
2114
+ var isActive = $parent.hasClass('open');
2055
2115
 
2056
2116
  if ((!isActive && e.which != 27) || (isActive && e.which == 27)) {
2057
- if (e.which == 27) $parent.find(toggle).trigger('focus')
2058
- return $this.trigger('click')
2117
+ if (e.which == 27) $parent.find(toggle).trigger('focus');
2118
+ return $this.trigger('click');
2059
2119
  }
2060
2120
 
2061
- var desc = ' li:not(.divider):visible a'
2062
- var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc)
2121
+ var desc = ' li:not(.divider):visible a';
2122
+ var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc);
2063
2123
 
2064
- if (!$items.length) return
2124
+ if (!$items.length) return;
2065
2125
 
2066
- var index = $items.index(e.target)
2126
+ var index = $items.index(e.target);
2067
2127
 
2068
- if (e.which == 38 && index > 0) index-- // up
2069
- if (e.which == 40 && index < $items.length - 1) index++ // down
2070
- if (!~index) index = 0
2128
+ if (e.which == 38 && index > 0) index--; // up
2129
+ if (e.which == 40 && index < $items.length - 1) index++; // down
2130
+ if (!~index) index = 0;
2071
2131
 
2072
- $items.eq(index).trigger('focus')
2073
- }
2132
+ $items.eq(index).trigger('focus');
2133
+ };
2074
2134
 
2075
2135
  function clearMenus(e) {
2076
- if (e && e.which === 3) return
2077
- $(backdrop).remove()
2136
+ if (e && e.which === 3) return;
2137
+ $(backdrop).remove();
2078
2138
  $(toggle).each(function () {
2079
- var $this = $(this)
2080
- var $parent = getParent($this)
2081
- var relatedTarget = { relatedTarget: this }
2139
+ var $this = $(this);
2140
+ var $parent = getParent($this);
2141
+ var relatedTarget = { relatedTarget: this };
2082
2142
 
2083
- if (!$parent.hasClass('open')) return
2143
+ if (!$parent.hasClass('open')) return;
2084
2144
 
2085
- $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
2145
+ $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget));
2086
2146
 
2087
- if (e.isDefaultPrevented()) return
2147
+ if (e.isDefaultPrevented()) return;
2088
2148
 
2089
- $this.attr('aria-expanded', 'false')
2090
- $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
2091
- })
2149
+ $this.attr('aria-expanded', 'false');
2150
+ $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget);
2151
+ });
2092
2152
  }
2093
2153
 
2094
2154
  function getParent($this) {
2095
- var selector = $this.attr('data-target')
2155
+ var selector = $this.attr('data-target');
2096
2156
 
2097
2157
  if (!selector) {
2098
- selector = $this.attr('href')
2099
- selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
2158
+ selector = $this.attr('href');
2159
+ selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, ''); // strip for ie7
2100
2160
  }
2101
2161
 
2102
- var $parent = selector && $(selector)
2162
+ var $parent = selector && $(selector);
2103
2163
 
2104
- return $parent && $parent.length ? $parent : $this.parent()
2164
+ return $parent && $parent.length ? $parent : $this.parent();
2105
2165
  }
2106
2166
 
2107
2167
 
@@ -2110,27 +2170,27 @@
2110
2170
 
2111
2171
  function Plugin(option) {
2112
2172
  return this.each(function () {
2113
- var $this = $(this)
2114
- var data = $this.data('bs.dropdown')
2173
+ var $this = $(this);
2174
+ var data = $this.data('bs.dropdown');
2115
2175
 
2116
- if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
2117
- if (typeof option == 'string') data[option].call($this)
2118
- })
2176
+ if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)));
2177
+ if (typeof option == 'string') data[option].call($this);
2178
+ });
2119
2179
  }
2120
2180
 
2121
- var old = $.fn.dropdown
2181
+ var old = $.fn.dropdown;
2122
2182
 
2123
- $.fn.dropdown = Plugin
2124
- $.fn.dropdown.Constructor = Dropdown
2183
+ $.fn.dropdown = Plugin;
2184
+ $.fn.dropdown.Constructor = Dropdown;
2125
2185
 
2126
2186
 
2127
2187
  // DROPDOWN NO CONFLICT
2128
2188
  // ====================
2129
2189
 
2130
2190
  $.fn.dropdown.noConflict = function () {
2131
- $.fn.dropdown = old
2132
- return this
2133
- }
2191
+ $.fn.dropdown = old;
2192
+ return this;
2193
+ };
2134
2194
 
2135
2195
 
2136
2196
  // APPLY TO STANDARD DROPDOWN ELEMENTS
@@ -2138,11 +2198,11 @@
2138
2198
 
2139
2199
  $(document)
2140
2200
  .on('click.bs.dropdown.data-api', clearMenus)
2141
- .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
2201
+ .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation(); })
2142
2202
  .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
2143
2203
  .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
2144
2204
  .on('keydown.bs.dropdown.data-api', '[role="menu"]', Dropdown.prototype.keydown)
2145
- .on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown)
2205
+ .on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown);
2146
2206
 
2147
2207
  }(jQuery);
2148
2208
  }
@@ -2289,6 +2349,17 @@
2289
2349
 
2290
2350
  this.mainPlot = function() {
2291
2351
  var svg = mg_get_svg_child_of(args.target);
2352
+
2353
+ //remove any old legends if they exist
2354
+ svg.selectAll('.mg-line-legend').remove();
2355
+
2356
+ var legend_group;
2357
+ var this_legend;
2358
+ if (args.legend) {
2359
+ legend_group = svg.append('g')
2360
+ .attr('class', 'mg-line-legend');
2361
+ }
2362
+
2292
2363
  var g;
2293
2364
  var data_median = 0;
2294
2365
  var updateTransitionDuration = (args.transition_on_update) ? 1000 : 0;
@@ -2301,16 +2372,12 @@
2301
2372
  .x(args.scalefns.xf)
2302
2373
  .y0(args.scales.Y.range()[0])
2303
2374
  .y1(args.scalefns.yf)
2304
- .interpolate(args.interpolate);
2375
+ .interpolate(args.interpolate)
2376
+ .tension(args.interpolate_tension);
2305
2377
 
2306
2378
  //confidence band
2307
2379
  var confidence_area;
2308
-
2309
- //if it already exists, remove it
2310
- var existing_band = svg.selectAll('.mg-confidence-band');
2311
- if (!existing_band.empty()) {
2312
- existing_band.remove();
2313
- }
2380
+ var existing_band = svg.select('.mg-confidence-band');
2314
2381
 
2315
2382
  if (args.show_confidence_band) {
2316
2383
  confidence_area = d3.svg.area()
@@ -2323,25 +2390,28 @@
2323
2390
  var u = args.show_confidence_band[1];
2324
2391
  return args.scales.Y(d[u]);
2325
2392
  })
2326
- .interpolate(args.interpolate);
2393
+ .interpolate(args.interpolate)
2394
+ .tension(args.interpolate_tension);
2327
2395
  }
2328
2396
 
2329
2397
  //main line
2330
2398
  var line = d3.svg.line()
2331
2399
  .x(args.scalefns.xf)
2332
2400
  .y(args.scalefns.yf)
2333
- .interpolate(args.interpolate);
2334
-
2401
+ .interpolate(args.interpolate)
2402
+ .tension(args.interpolate_tension);
2335
2403
  //for animating line on first load
2336
2404
  var flat_line = d3.svg.line()
2337
2405
  .x(args.scalefns.xf)
2338
2406
  .y(function() { return args.scales.Y(data_median); })
2339
- .interpolate(args.interpolate);
2407
+ .interpolate(args.interpolate)
2408
+ .tension(args.interpolate_tension);
2340
2409
 
2341
2410
 
2342
2411
  //for building the optional legend
2343
2412
  var legend = '';
2344
2413
  var this_data;
2414
+ var confidenceBand;
2345
2415
 
2346
2416
  for (var i = args.data.length - 1; i >= 0; i--) {
2347
2417
  this_data = args.data[i];
@@ -2356,10 +2426,20 @@
2356
2426
 
2357
2427
  //add confidence band
2358
2428
  if (args.show_confidence_band) {
2359
- svg.append('path')
2360
- .attr('class', 'mg-confidence-band')
2361
- .attr('d', confidence_area(args.data[i]))
2362
- .attr('clip-path', 'url(#mg-plot-window-' + mg_strip_punctuation(args.target) + ')');
2429
+ if (!existing_band.empty()) {
2430
+ confidenceBand = existing_band
2431
+ .transition()
2432
+ .duration(function() {
2433
+ return (args.transition_on_update) ? 1000 : 0;
2434
+ });
2435
+ } else {
2436
+ confidenceBand = svg.append('path')
2437
+ .attr('class', 'mg-confidence-band');
2438
+ }
2439
+
2440
+ confidenceBand
2441
+ .attr('d', confidence_area(args.data[i]))
2442
+ .attr('clip-path', 'url(#mg-plot-window-'+ mg_strip_punctuation(args.target)+')');
2363
2443
  }
2364
2444
 
2365
2445
  //add the area
@@ -2376,7 +2456,7 @@
2376
2456
  .transition()
2377
2457
  .duration(updateTransitionDuration)
2378
2458
  .attr('d', area(args.data[i]))
2379
- .attr('clip-path', 'url(#mg-plot-window)');
2459
+ .attr('clip-path', 'url(#mg-plot-window-'+ mg_strip_punctuation(args.target)+')');
2380
2460
  } else { //otherwise, add the area
2381
2461
  svg.append('path')
2382
2462
  .attr('class', 'mg-main-area ' + 'mg-area' + (line_id) + '-color')
@@ -2424,14 +2504,92 @@
2424
2504
  }
2425
2505
  }
2426
2506
 
2507
+ var the_line = svg.select('.mg-line' + (line_id) + '-color');
2508
+ if (args.missing_is_hidden && the_line.attr('d') !== null) {
2509
+ var bits = the_line.attr('d').split('L');
2510
+ var zero = args.scales.Y(0) + 42.1234;
2511
+ var dasharray = [];
2512
+ var singleton_point_length = 2;
2513
+
2514
+ var x_y,
2515
+ x_y_plus_1,
2516
+ x,
2517
+ y,
2518
+ x_plus_1,
2519
+ y_plus_1,
2520
+ segment_length,
2521
+ cumulative_segment_length = 0;
2522
+
2523
+ bits[0] = bits[0].replace('M', '');
2524
+ bits[bits.length - 1] = bits[bits.length - 1].replace('Z', '');
2525
+
2526
+ //if we have a min_x, turn the line off first
2527
+ if (args.min_x) {
2528
+ dasharray.push(0);
2529
+ }
2530
+
2531
+ //build the stroke-dasharray pattern
2532
+ for (var j = 0; j < bits.length - 1; j++) {
2533
+ x_y = bits[j].split(',');
2534
+ x_y_plus_1 = bits[j + 1].split(',');
2535
+ x = Number(x_y[0]);
2536
+ y = Number(x_y[1]);
2537
+ x_plus_1 = Number(x_y_plus_1[0]);
2538
+ y_plus_1 = Number(x_y_plus_1[1]);
2539
+
2540
+ segment_length = Math.sqrt(Math.pow(x - x_plus_1, 2) + Math.pow(y - y_plus_1, 2));
2541
+
2542
+ //do we need to either cover or clear the current stroke
2543
+ if (y_plus_1 == zero && y != zero) {
2544
+ dasharray.push(cumulative_segment_length || singleton_point_length);
2545
+ cumulative_segment_length = (cumulative_segment_length)
2546
+ ? segment_length
2547
+ : segment_length - singleton_point_length;
2548
+ } else if (y_plus_1 != zero && y == zero) { //switching on line
2549
+ dasharray.push(cumulative_segment_length += segment_length);
2550
+ cumulative_segment_length = 0;
2551
+ } else {
2552
+ cumulative_segment_length += segment_length;
2553
+ }
2554
+ }
2555
+
2556
+ //fear not, end bit of line, ye too shall be covered
2557
+ if (dasharray.length > 0) {
2558
+ dasharray.push(the_line.node().getTotalLength() - dasharray[dasharray.length - 1]);
2559
+
2560
+ svg.select('.mg-line' + (line_id) + '-color')
2561
+ .attr('stroke-dasharray', dasharray.join());
2562
+ }
2563
+ }
2564
+
2427
2565
  //build legend
2428
2566
  if (args.legend) {
2429
- legend = "<span class='mg-line" + line_id + "-legend-color'>&mdash; "
2430
- + args.legend[i] + "&nbsp; </span>" + legend;
2567
+ if (is_array(args.legend)) {
2568
+ this_legend = args.legend[i];
2569
+ } else if (is_function(args.legend)) {
2570
+ this_legend = args.legend(this_data);
2571
+ }
2572
+
2573
+ if (args.legend_target) {
2574
+ legend = "<span class='mg-line" + line_id + "-legend-color'>&mdash; "
2575
+ + this_legend + "&nbsp; </span>" + legend;
2576
+ } else {
2577
+ var last_point = this_data[this_data.length-1];
2578
+
2579
+ legend_group.append('svg:text')
2580
+ .classed('mg-line' + (line_id) + '-legend-color', true)
2581
+ .attr('x', args.scalefns.xf(last_point))
2582
+ .attr('dx', args.buffer)
2583
+ .attr('y', args.scalefns.yf(last_point))
2584
+ .attr('dy', '.35em')
2585
+ .text(this_legend);
2586
+
2587
+ preventVerticalOverlap(legend_group.selectAll('.mg-line-legend text')[0], args);
2588
+ }
2431
2589
  }
2432
2590
  }
2433
2591
 
2434
- if (args.legend) {
2592
+ if (args.legend_target) {
2435
2593
  d3.select(args.legend_target).html(legend);
2436
2594
  }
2437
2595
 
@@ -2668,8 +2826,27 @@
2668
2826
 
2669
2827
  //if the dataset is of length 1, trigger the rollover for our solitary rollover rect
2670
2828
  if (args.data.length == 1 && args.data[0].length == 1) {
2671
- d3.select('.mg-rollover-rect .mg-line1-color')
2829
+ svg.select('.mg-rollover-rect rect')
2672
2830
  .on('mouseover')(args.data[0][0], 0);
2831
+ } else if (args.data.length > 1) {
2832
+ //otherwise, trigger it for an appropriate line in a multi-line chart
2833
+ for (var i = 0; i < args.data.length; i++) {
2834
+ var j = i + 1;
2835
+
2836
+ if (args.custom_line_color_map.length > 0
2837
+ && args.custom_line_color_map[i] !== undefined
2838
+ ) {
2839
+ j = args.custom_line_color_map[i];
2840
+ }
2841
+
2842
+ if (args.data[i].length == 1) {
2843
+ svg.selectAll('.mg-voronoi .mg-line' + j + '-color')
2844
+ .on('mouseover')(args.data[i][0], 0);
2845
+
2846
+ svg.selectAll('.mg-voronoi .mg-line' + j + '-color')
2847
+ .on('mouseout')(args.data[i][0], 0);
2848
+ }
2849
+ }
2673
2850
  }
2674
2851
 
2675
2852
  return this;
@@ -2693,20 +2870,17 @@
2693
2870
  }
2694
2871
 
2695
2872
  return function(d, i) {
2696
-
2697
2873
  if (args.aggregate_rollover && args.data.length > 1) {
2698
-
2699
2874
  // hide the circles in case a non-contiguous series is present
2700
2875
  svg.selectAll('circle.mg-line-rollover-circle')
2701
2876
  .style('opacity', 0);
2702
2877
 
2703
2878
  d.values.forEach(function(datum) {
2704
-
2705
2879
  if (datum[args.x_accessor] >= args.processed.min_x &&
2706
2880
  datum[args.x_accessor] <= args.processed.max_x &&
2707
2881
  datum[args.y_accessor] >= args.processed.min_y &&
2708
2882
  datum[args.y_accessor] <= args.processed.max_y
2709
- ){
2883
+ ) {
2710
2884
  var circle = svg.select('circle.mg-line' + datum.line_id + '-color')
2711
2885
  .attr({
2712
2886
  'cx': function() {
@@ -2720,15 +2894,21 @@
2720
2894
  .style('opacity', 1);
2721
2895
  }
2722
2896
  });
2723
- } else {
2897
+ } else if (args.missing_is_hidden
2898
+ && d[args.y_accessor] == 0
2899
+ && d['missing']
2900
+ ) {
2724
2901
 
2902
+ //disable rollovers for hidden parts of the line
2903
+ return;
2904
+ } else {
2725
2905
  //show circle on mouse-overed rect
2726
2906
  if (d[args.x_accessor] >= args.processed.min_x &&
2727
2907
  d[args.x_accessor] <= args.processed.max_x &&
2728
2908
  d[args.y_accessor] >= args.processed.min_y &&
2729
2909
  d[args.y_accessor] <= args.processed.max_y
2730
- ){
2731
- svg.selectAll('circle.mg-line-rollover-circle')
2910
+ ) {
2911
+ svg.selectAll('circle.mg-line-rollover-circle.mg-area' + d.line_id + '-color')
2732
2912
  .attr('class', "")
2733
2913
  .attr('class', 'mg-area' + d.line_id + '-color')
2734
2914
  .classed('mg-line-rollover-circle', true)
@@ -2885,16 +3065,33 @@
2885
3065
  });
2886
3066
  }
2887
3067
 
2888
- //remove active datapoint text on mouse out, except if we have a single
2889
- svg.selectAll('circle.mg-line-rollover-circle')
2890
- .style('opacity', function() {
2891
- if (args.data.length == 1 && args.data[0].length == 1) {
3068
+ //remove all active data points when aggregate_rollover is enabled
3069
+ if(args.aggregate_rollover) {
3070
+ svg.selectAll('circle.mg-line-rollover-circle')
3071
+ .style('opacity', function() {
3072
+ return 0;
3073
+ });
3074
+ //remove active data point text on mouse out, except if we have a single point
3075
+ } else {
3076
+ svg.selectAll('circle.mg-line-rollover-circle.mg-area' + (d.line_id) + '-color')
3077
+ .style('opacity', function() {
3078
+ var id = d.line_id - 1;
3079
+
3080
+ if (args.custom_line_color_map.length > 0
3081
+ && args.custom_line_color_map.indexOf(d.line_id) !== undefined
3082
+ ) {
3083
+ id = args.custom_line_color_map.indexOf(d.line_id);
3084
+ }
3085
+
3086
+ if (args.data[id].length == 1) {
3087
+ //if (args.data.length === 1 && args.data[0].length === 1) {
2892
3088
  return 1;
2893
3089
  }
2894
3090
  else {
2895
3091
  return 0;
2896
3092
  }
2897
3093
  });
3094
+ }
2898
3095
 
2899
3096
  svg.select('.mg-active-datapoint')
2900
3097
  .text('');
@@ -3270,8 +3467,8 @@
3270
3467
  }
3271
3468
 
3272
3469
  //trigger mouseover on all points for this class name in .linked charts
3273
- if (args.linked && !globals.link) {
3274
- globals.link = true;
3470
+ if (args.linked && !MG.globals.link) {
3471
+ MG.globals.link = true;
3275
3472
 
3276
3473
  //trigger mouseover on matching point in .linked charts
3277
3474
  d3.selectAll('.mg-voronoi .path-' + i)
@@ -3310,8 +3507,8 @@
3310
3507
  var svg = mg_get_svg_child_of(args.target);
3311
3508
 
3312
3509
  return function(d,i) {
3313
- if (args.linked && globals.link) {
3314
- globals.link = false;
3510
+ if (args.linked && MG.globals.link) {
3511
+ MG.globals.link = false;
3315
3512
 
3316
3513
  d3.selectAll('.mg-voronoi .path-' + i)
3317
3514
  .each(function() {
@@ -3394,7 +3591,7 @@
3394
3591
  this.mainPlot = function() {
3395
3592
  var svg = mg_get_svg_child_of(args.target);
3396
3593
  var data = args.data[0];
3397
- var barplot = svg.select('.mg-barplot');
3594
+ var barplot = svg.select('g.mg-barplot');
3398
3595
  var fresh_render = barplot.empty();
3399
3596
 
3400
3597
  var bars;
@@ -3407,69 +3604,84 @@
3407
3604
  var transition_duration = args.transition_duration || 1000;
3408
3605
 
3409
3606
  // draw the plot on first render
3410
- if (fresh_render) {
3607
+ if (barplot.empty()) {
3411
3608
  barplot = svg.append('g')
3412
3609
  .classed('mg-barplot', true);
3610
+ }
3413
3611
 
3414
- bars = barplot.selectAll('.mg-bar')
3415
- .data(data)
3416
- .enter()
3417
- .append('rect')
3418
- .classed('mg-bar', true);
3612
+ bars = bars = barplot.selectAll('.mg-bar')
3613
+ .data(data);
3419
3614
 
3420
- if (args.predictor_accessor) {
3421
- predictor_bars = barplot.selectAll('.mg-bar-prediction')
3422
- .data(data)
3423
- .enter()
3424
- .append('rect')
3425
- .classed('mg-bar-prediction', true);
3426
- }
3615
+ bars.exit().remove();
3427
3616
 
3428
- if (args.baseline_accessor) {
3429
- baseline_marks = barplot.selectAll('.mg-bar-baseline')
3430
- .data(data)
3431
- .enter()
3432
- .append('line')
3433
- .classed('mg-bar-baseline', true);
3434
- }
3617
+ bars.enter().append('rect')
3618
+ .classed('mg-bar', true);
3619
+
3620
+ if (args.predictor_accessor) {
3621
+ predictor_bars = barplot.selectAll('.mg-bar-prediction')
3622
+ .data(data);
3623
+
3624
+ predictor_bars.exit().remove();
3625
+
3626
+ predictor_bars.enter().append('rect')
3627
+ .classed('mg-bar-prediction', true);
3435
3628
  }
3436
- // setup vars with the existing elements
3437
- // TODO: deal with changing data sets - i.e. more/less, different labels etc.
3438
- else {
3439
- // move the barplot after the axes so it doesn't overlap
3440
- //$(svg.node()).find('.mg-y-axis').after($(barplot.node()).detach());
3441
- svg.select('.mg-y-axis').node().parentNode.appendChild(barplot.node());
3442
-
3443
- console.log('waylee');
3444
- bars = barplot.selectAll('rect.mg-bar');
3445
3629
 
3446
- if (args.predictor_accessor) {
3447
- predictor_bars = barplot.selectAll('.mg-bar-prediction');
3630
+ if (args.baseline_accessor) {
3631
+ baseline_marks = barplot.selectAll('.mg-bar-baseline')
3632
+ .data(data);
3633
+
3634
+ baseline_marks.exit().remove();
3635
+
3636
+ baseline_marks.enter().append('line')
3637
+ .classed('mg-bar-baseline', true);
3638
+ }
3639
+
3640
+ var appropriate_size;
3641
+
3642
+
3643
+ // setup transitions
3644
+ if (should_transition) {
3645
+ bars = bars.transition()
3646
+ .duration(transition_duration);
3647
+
3648
+ if (predictor_bars) {
3649
+ predictor_bars = predictor_bars.transition()
3650
+ .duration(transition_duration);
3448
3651
  }
3449
3652
 
3450
- if (args.baseline_accessor) {
3451
- baseline_marks = barplot.selectAll('.mg-bar-baseline');
3653
+ if (baseline_marks) {
3654
+ baseline_marks = baseline_marks.transition()
3655
+ .duration(transition_duration);
3452
3656
  }
3453
3657
  }
3454
3658
 
3455
- var appropriate_size;
3456
3659
 
3457
3660
  if (this.is_vertical) {
3458
3661
  appropriate_size = args.scales.X.rangeBand()/1.5;
3459
3662
 
3460
3663
  if (perform_load_animation) {
3461
- bars.attr('height', 0)
3462
- .attr('y', args.scales.Y(0));
3463
- }
3664
+ bars.attr({
3665
+ height: 0,
3666
+ y: args.scales.Y(0)
3667
+ });
3464
3668
 
3465
- if (should_transition) {
3466
- bars = bars.transition()
3467
- .duration(transition_duration);
3669
+ if (predictor_bars) {
3670
+ predictor_bars.attr({
3671
+ height: 0,
3672
+ y: args.scales.Y(0)
3673
+ });
3674
+ }
3675
+
3676
+ if (baseline_marks) {
3677
+ baseline_marks.attr({
3678
+ y1: args.scales.Y(0),
3679
+ y2: args.scales.Y(0)
3680
+ });
3681
+ }
3468
3682
  }
3469
3683
 
3470
- bars.attr('y', function(d) {
3471
- return args.scales.Y(0) - (args.scales.Y(0) - args.scalefns.yf(d));
3472
- })
3684
+ bars.attr('y', args.scalefns.yf)
3473
3685
  .attr('x', function(d) {
3474
3686
  return args.scalefns.xf(d) + appropriate_size/2;
3475
3687
  })
@@ -3478,21 +3690,12 @@
3478
3690
  return 0 - (args.scalefns.yf(d) - args.scales.Y(0));
3479
3691
  });
3480
3692
 
3693
+
3481
3694
  if (args.predictor_accessor) {
3482
3695
  pp = args.predictor_proportion;
3483
3696
  pp0 = pp-1;
3484
3697
 
3485
- if (perform_load_animation) {
3486
- predictor_bars.attr('height', 0)
3487
- .attr('y', args.scales.Y(0));
3488
- }
3489
-
3490
- if (should_transition) {
3491
- predictor_bars = predictor_bars.transition()
3492
- .duration(transition_duration);
3493
- }
3494
-
3495
- // thick line through bar;
3698
+ // thick line through bar;
3496
3699
  predictor_bars
3497
3700
  .attr('y', function(d) {
3498
3701
  return args.scales.Y(0) - (args.scales.Y(0) - args.scales.Y(d[args.predictor_accessor]));
@@ -3509,15 +3712,6 @@
3509
3712
  if (args.baseline_accessor) {
3510
3713
  pp = args.predictor_proportion;
3511
3714
 
3512
- if (perform_load_animation) {
3513
- baseline_marks.attr({y1: args.scales.Y(0), y2: args.scales.Y(0)});
3514
- }
3515
-
3516
- if (should_transition) {
3517
- baseline_marks = baseline_marks.transition()
3518
- .duration(transition_duration);
3519
- }
3520
-
3521
3715
  baseline_marks
3522
3716
  .attr('x1', function(d) {
3523
3717
  return args.scalefns.xf(d)+appropriate_size/2-appropriate_size/pp + appropriate_size/2;
@@ -3533,11 +3727,17 @@
3533
3727
 
3534
3728
  if (perform_load_animation) {
3535
3729
  bars.attr('width', 0);
3536
- }
3537
3730
 
3538
- if (should_transition) {
3539
- bars = bars.transition()
3540
- .duration(transition_duration);
3731
+ if (predictor_bars) {
3732
+ predictor_bars.attr('width', 0);
3733
+ }
3734
+
3735
+ if (baseline_marks) {
3736
+ baseline_marks.attr({
3737
+ x1: args.scales.X(0),
3738
+ x2: args.scales.X(0)
3739
+ });
3740
+ }
3541
3741
  }
3542
3742
 
3543
3743
  bars.attr('x', args.scales.X(0))
@@ -3554,15 +3754,6 @@
3554
3754
  pp = args.predictor_proportion;
3555
3755
  pp0 = pp-1;
3556
3756
 
3557
- if (perform_load_animation) {
3558
- predictor_bars.attr('width', 0);
3559
- }
3560
-
3561
- if (should_transition) {
3562
- predictor_bars = predictor_bars.transition()
3563
- .duration(transition_duration);
3564
- }
3565
-
3566
3757
  // thick line through bar;
3567
3758
  predictor_bars
3568
3759
  .attr('x', args.scales.X(0))
@@ -3578,16 +3769,6 @@
3578
3769
  if (args.baseline_accessor) {
3579
3770
  pp = args.predictor_proportion;
3580
3771
 
3581
- if (perform_load_animation) {
3582
- baseline_marks
3583
- .attr({x1: args.scales.X(0), x2: args.scales.X(0)});
3584
- }
3585
-
3586
- if (should_transition) {
3587
- baseline_marks = baseline_marks.transition()
3588
- .duration(transition_duration);
3589
- }
3590
-
3591
3772
  baseline_marks
3592
3773
  .attr('x1', function(d) { return args.scales.X(d[args.baseline_accessor]); })
3593
3774
  .attr('x2', function(d) { return args.scales.X(d[args.baseline_accessor]); })
@@ -3911,7 +4092,7 @@
3911
4092
 
3912
4093
  if (this_format === 'percentage') formatter = d3.format('%p');
3913
4094
  if (this_format === 'count') formatter = d3.format("0,000");
3914
- if (this_format === 'temperature') formatter = function(t) { return t +'º'; };
4095
+ if (this_format === 'temperature') formatter = function(t) { return t +'°'; };
3915
4096
 
3916
4097
  td_text = formatter(td_text);
3917
4098
  }
@@ -3957,25 +4138,42 @@
3957
4138
  this.args = args;
3958
4139
 
3959
4140
  this.init = function(args) {
3960
- chart_title(args);
4141
+ var svg_width,
4142
+ svg_height;
4143
+
4144
+ svg_width = args.width;
4145
+ svg_height = args.height;
4146
+
4147
+ if (args.full_width) {
4148
+ // get parent element
4149
+ svg_width = get_width(args.target);
4150
+ }
4151
+
4152
+ if (args.full_height) {
4153
+ svg_height = get_height(args.target);
4154
+ }
3961
4155
 
4156
+ chart_title(args);
3962
4157
  // create svg if one doesn't exist
3963
4158
  d3.select(args.target).selectAll('svg').data([args])
3964
4159
  .enter().append('svg')
3965
- .attr('width', args.width)
3966
- .attr('height', args.height);
4160
+ .attr('width', svg_width)
4161
+ .attr('height', svg_height);
3967
4162
 
3968
4163
  var svg = mg_get_svg_child_of(args.target);
3969
-
4164
+
3970
4165
  // has the width or height changed?
3971
- if (args.width !== Number(svg.attr('width'))) {
3972
- svg.attr('width', args.width);
4166
+ if (svg_width !== Number(svg.attr('width'))) {
4167
+ svg.attr('width', svg_width);
3973
4168
  }
3974
4169
 
3975
- if (args.height !== Number(svg.attr('height'))) {
3976
- svg.attr('height', args.height);
4170
+ if (svg_height !== Number(svg.attr('height'))) {
4171
+ svg.attr('height', svg_height);
3977
4172
  }
3978
4173
 
4174
+ // @todo need to reconsider how we handle automatic scaling
4175
+ svg.attr('viewBox', '0 0 ' + svg_width + ' ' + svg_height);
4176
+
3979
4177
  // delete child elements
3980
4178
  d3.select(args.target).selectAll('svg *').remove();
3981
4179
 
@@ -3996,11 +4194,11 @@
3996
4194
 
3997
4195
  args.scales.X = d3.scale.linear()
3998
4196
  .domain([0, data.length])
3999
- .range([args.left + args.buffer, args.width - args.right - args.buffer]);
4197
+ .range([args.left + args.buffer, svg_width - args.right - args.buffer]);
4000
4198
 
4001
4199
  args.scales.Y = d3.scale.linear()
4002
4200
  .domain([-2, 2])
4003
- .range([args.height - args.bottom - args.buffer*2, args.top]);
4201
+ .range([svg_height - args.bottom - args.buffer*2, args.top]);
4004
4202
 
4005
4203
  args.scalefns.xf = function(di) { return args.scales.X(di.x); };
4006
4204
  args.scalefns.yf = function(di) { return args.scales.Y(di.y); };
@@ -4023,8 +4221,8 @@
4023
4221
  .classed('mg-missing-background', true)
4024
4222
  .attr('x', args.buffer)
4025
4223
  .attr('y', args.buffer)
4026
- .attr('width', args.width-args.buffer*2)
4027
- .attr('height', args.height-args.buffer*2)
4224
+ .attr('width', svg_width-args.buffer*2)
4225
+ .attr('height', svg_height-args.buffer*2)
4028
4226
  .attr('rx',15)
4029
4227
  .attr('ry', 15);
4030
4228
 
@@ -4041,8 +4239,8 @@
4041
4239
  svg.selectAll('.mg-missing-text').data([args.missing_text])
4042
4240
  .enter().append('text')
4043
4241
  .attr('class', 'mg-missing-text')
4044
- .attr('x', args.width / 2)
4045
- .attr('y', args.height / 2)
4242
+ .attr('x', svg_width / 2)
4243
+ .attr('y', svg_height / 2)
4046
4244
  .attr('dy', '.50em')
4047
4245
  .attr('text-anchor', 'middle')
4048
4246
  .text(args.missing_text);
@@ -4058,18 +4256,32 @@
4058
4256
  'use strict';
4059
4257
 
4060
4258
  // We need to account for a few data format cases:
4061
- // 1. [{key:__, value:__}, ...] // unnested obj-arrays
4062
- // 2. [[{key:__, value:__}, ...], [{key:__, value:__}, ...]] // nested obj-arrays
4063
- // 3. [[4323, 2343],..] // unnested 2d array
4064
- // 4. [[[4323, 2343],..] , [[4323, 2343],..]] // nested 2d array
4065
- if (args.chart_type === 'line') {
4066
- var is_unnested_obj_array = (args.data[0] instanceof Object && !(args.data[0] instanceof Array));
4067
- var is_unnested_array_of_arrays = (
4068
- args.data[0] instanceof Array &&
4069
- !(args.data[0][0] instanceof Object &&
4070
- !(args.data[0][0] instanceof Date)));
4259
+ // #1 [{key:__, value:__}, ...] // unnested obj-arrays
4260
+ // #2 [[{key:__, value:__}, ...], [{key:__, value:__}, ...]] // nested obj-arrays
4261
+ // #3 [[4323, 2343],..] // unnested 2d array
4262
+ // #4 [[[4323, 2343],..] , [[4323, 2343],..]] // nested 2d array
4263
+
4264
+ var _is_nested_array = is_array_of_arrays(args.data);
4265
+
4266
+ args.array_of_objects = false;
4267
+ args.array_of_arrays = false;
4268
+ args.nested_array_of_arrays = false;
4269
+ args.nested_array_of_objects = false;
4270
+
4271
+ if (_is_nested_array) {
4272
+ args.nested_array_of_objects = args.data.map(function(d){
4273
+ return is_array_of_objects_or_empty(d);
4274
+ }); // Case #2
4275
+ args.nested_array_of_arrays = args.data.map(function(d){
4276
+ return is_array_of_arrays(d);
4277
+ }) // Case #4
4278
+ } else {
4279
+ args.array_of_objects = is_array_of_objects(args.data); // Case #1
4280
+ args.array_of_arrays = is_array_of_arrays(args.data); // Case #3
4281
+ }
4071
4282
 
4072
- if (is_unnested_obj_array || is_unnested_array_of_arrays) {
4283
+ if (args.chart_type === 'line') {
4284
+ if (args.array_of_objects || args.array_of_arrays) {
4073
4285
  args.data = [args.data];
4074
4286
  }
4075
4287
  } else {
@@ -4112,12 +4324,21 @@
4112
4324
  function process_line(args) {
4113
4325
  'use strict';
4114
4326
  //do we have a time-series?
4115
- var is_time_series = args.data[0][0][args.x_accessor] instanceof Date
4116
- ? true
4117
- : false;
4327
+ var is_time_series = d3.sum(args.data.map(function(series) {
4328
+ return series.length > 0 && series[0][args.x_accessor] instanceof Date;
4329
+ })) > 0;
4330
+
4331
+ // var is_time_series = args.data[0][0][args.x_accessor] instanceof Date
4332
+ // ? true
4333
+ // : false;
4334
+
4335
+ //force linear interpolation when missing_is_hidden is enabled
4336
+ if (args.missing_is_hidden) {
4337
+ args.interpolate = 'linear';
4338
+ }
4118
4339
 
4119
4340
  //are we replacing missing y values with zeros?
4120
- if (args.missing_is_zero
4341
+ if ((args.missing_is_zero || args.missing_is_hidden)
4121
4342
  && args.chart_type === 'line'
4122
4343
  && is_time_series
4123
4344
  ) {
@@ -4162,6 +4383,7 @@
4162
4383
  if (!existing_o) {
4163
4384
  o[args.x_accessor] = new Date(d);
4164
4385
  o[args.y_accessor] = 0;
4386
+ o['missing'] = true; //we want to distinguish between zero-value and missing observations
4165
4387
  processed_data.push(o);
4166
4388
  }
4167
4389
  //otherwise, use the existing object for that date
@@ -4195,6 +4417,7 @@
4195
4417
 
4196
4418
  // histogram data is always single dimension
4197
4419
  var our_data = args.data[0];
4420
+
4198
4421
  var extracted_data;
4199
4422
  if (args.binned === false) {
4200
4423
  // use d3's built-in layout.histogram functionality to compute what you need.
@@ -4263,7 +4486,6 @@
4263
4486
  var data_accessor = args.bar_orientation === 'vertical' ? args.y_accessor : args.x_accessor;
4264
4487
 
4265
4488
  args.categorical_variables = [];
4266
-
4267
4489
  if (args.binned === false) {
4268
4490
  if (typeof(our_data[0]) === 'object') {
4269
4491
  // we are dealing with an array of objects. Extract the data value of interest.
@@ -4651,13 +4873,151 @@
4651
4873
  return data;
4652
4874
  };
4653
4875
 
4876
+
4877
+ function is_array(thing){
4878
+ return Object.prototype.toString.call(thing) === '[object Array]';
4879
+ }
4880
+
4881
+ function is_function(thing){
4882
+ return Object.prototype.toString.call(thing) === '[object Function]';
4883
+ }
4884
+
4885
+ function is_empty_array(thing){
4886
+ return is_array(thing) && thing.length==0;
4887
+ }
4888
+
4889
+ function is_object(thing){
4890
+ return Object.prototype.toString.call(thing) === '[object Object]';
4891
+ }
4892
+
4893
+ function is_array_of_arrays(data){
4894
+ var all_elements = data.map(function(d){return is_array(d)===true && d.length>0});
4895
+ return d3.sum(all_elements) === data.length;
4896
+ }
4897
+
4898
+ function is_array_of_objects(data){
4899
+ // is every element of data an object?
4900
+ var all_elements = data.map(function(d){return is_object(d)===true});
4901
+ return d3.sum(all_elements) === data.length;
4902
+ }
4903
+
4904
+ function is_array_of_objects_or_empty(data){
4905
+ return is_empty_array(data) || is_array_of_objects(data);
4906
+ }
4907
+
4908
+
4909
+ function preventHorizontalOverlap(labels, args) {
4910
+ if (!labels || labels.length == 1) {
4911
+ return;
4912
+ }
4913
+
4914
+ //see if each of our labels overlaps any of the other labels
4915
+ for (var i = 0; i < labels.length; i++) {
4916
+ //if so, nudge it up a bit, if the label it intersects hasn't already been nudged
4917
+ if (isHorizontallyOverlapping(labels[i], labels)) {
4918
+ var node = d3.select(labels[i]);
4919
+ var newY = +node.attr('y');
4920
+ if (newY + 8 == args.top) {
4921
+ newY = args.top - 16;
4922
+ }
4923
+ node.attr('y', newY);
4924
+ }
4925
+ }
4926
+ }
4927
+
4928
+ function preventVerticalOverlap(labels, args) {
4929
+ if (!labels || labels.length == 1) {
4930
+ return;
4931
+ }
4932
+
4933
+ labels.sort(function(b,a){
4934
+ return d3.select(a).attr('y') - d3.select(b).attr('y');
4935
+ });
4936
+
4937
+ labels.reverse();
4938
+
4939
+ var overlap_amount, label_i, label_j;
4940
+
4941
+ //see if each of our labels overlaps any of the other labels
4942
+ for (var i = 0; i < labels.length; i++) {
4943
+ //if so, nudge it up a bit, if the label it intersects hasn't already been nudged
4944
+ label_i = d3.select(labels[i]).text();
4945
+
4946
+ for (var j = 0; j < labels.length; j ++) {
4947
+ label_j = d3.select(labels[j]).text();
4948
+ overlap_amount = isVerticallyOverlapping(labels[i], labels[j]);
4949
+
4950
+ if (overlap_amount !== false && label_i !== label_j) {
4951
+ var node = d3.select(labels[i]);
4952
+ var newY = +node.attr('y');
4953
+ newY = newY + overlap_amount;
4954
+ node.attr('y', newY);
4955
+ }
4956
+ }
4957
+ }
4958
+ }
4959
+
4960
+ function isVerticallyOverlapping(element, sibling) {
4961
+ var element_bbox = element.getBoundingClientRect();
4962
+ var sibling_bbox = sibling.getBoundingClientRect();
4963
+
4964
+ if (element_bbox.top <= sibling_bbox.bottom && element_bbox.top >= sibling_bbox.top) {
4965
+ return sibling_bbox.bottom - element_bbox.top;
4966
+ }
4967
+
4968
+ return false;
4969
+ }
4970
+
4971
+ function isHorizontallyOverlapping(element, labels) {
4972
+ var element_bbox = element.getBoundingClientRect();
4973
+
4974
+ for (var i = 0; i < labels.length; i++) {
4975
+ if (labels[i] == element) {
4976
+ continue;
4977
+ }
4978
+
4979
+ //check to see if this label overlaps with any of the other labels
4980
+ var sibling_bbox = labels[i].getBoundingClientRect();
4981
+ if (element_bbox.top === sibling_bbox.top &&
4982
+ !(sibling_bbox.left > element_bbox.right || sibling_bbox.right < element_bbox.left)
4983
+ ) {
4984
+ return true;
4985
+ }
4986
+ }
4987
+
4988
+ return false;
4989
+ }
4990
+
4654
4991
  function mg_get_svg_child_of(selector_or_node) {
4655
4992
  return d3.select(selector_or_node).select('svg');
4656
4993
  }
4657
4994
 
4995
+ function mg_flatten_array(arr) {
4996
+ var flat_data = [];
4997
+ return flat_data.concat.apply(flat_data, arr);
4998
+ }
4999
+
4658
5000
  function mg_strip_punctuation(s) {
4659
- var punctuationless = s.replace(/[^a-zA-Z0-9 _]+/g, '');
5001
+ var processed_s;
5002
+
5003
+ if (typeof(s) == 'string') {
5004
+ processed_s = s;
5005
+ } else {
5006
+ // args.target is
5007
+ if (s.id != '') {
5008
+ processed_s = s.id;
5009
+ } else if (args.target.className != '') {
5010
+ processed_s = s.className;
5011
+ } else if (args.target.nodeName !='') {
5012
+ processed_s = s.nodeName;
5013
+ } else {
5014
+ console.warn('The specified target element ' + s + ' has no unique attributes.');
5015
+ }
5016
+ }
5017
+
5018
+ var punctuationless = processed_s.replace(/[^a-zA-Z0-9 _]+/g, '');
4660
5019
  var finalString = punctuationless.replace(/ +?/g, "");
5020
+
4661
5021
  return finalString;
4662
5022
  }
4663
5023