metrics-graphics-rails 2.1.3.2 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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