chartist-rails 0.0.1 → 0.9.4

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.
@@ -14,7 +14,7 @@
14
14
  }
15
15
  }(this, function () {
16
16
 
17
- /* Chartist.js 0.7.4
17
+ /* Chartist.js 0.9.4
18
18
  * Copyright © 2015 Gion Kunz
19
19
  * Free to use under the WTFPL license.
20
20
  * http://www.wtfpl.net/
@@ -25,7 +25,7 @@
25
25
  * @module Chartist.Core
26
26
  */
27
27
  var Chartist = {
28
- version: '0.7.4'
28
+ version: '0.9.4'
29
29
  };
30
30
 
31
31
  (function (window, document, Chartist) {
@@ -68,7 +68,7 @@ var Chartist = {
68
68
  var sources = Array.prototype.slice.call(arguments, 1);
69
69
  sources.forEach(function(source) {
70
70
  for (var prop in source) {
71
- if (typeof source[prop] === 'object' && !(source[prop] instanceof Array)) {
71
+ if (typeof source[prop] === 'object' && source[prop] !== null && !(source[prop] instanceof Array)) {
72
72
  target[prop] = Chartist.extend({}, target[prop], source[prop]);
73
73
  } else {
74
74
  target[prop] = source[prop];
@@ -154,7 +154,33 @@ var Chartist = {
154
154
  * @return {*}
155
155
  */
156
156
  Chartist.sum = function(previous, current) {
157
- return previous + current;
157
+ return previous + (current ? current : 0);
158
+ };
159
+
160
+ /**
161
+ * Multiply helper to be used in `Array.map` for multiplying each value of an array with a factor.
162
+ *
163
+ * @memberof Chartist.Core
164
+ * @param {Number} factor
165
+ * @returns {Function} Function that can be used in `Array.map` to multiply each value in an array
166
+ */
167
+ Chartist.mapMultiply = function(factor) {
168
+ return function(num) {
169
+ return num * factor;
170
+ };
171
+ };
172
+
173
+ /**
174
+ * Add helper to be used in `Array.map` for adding a addend to each value of an array.
175
+ *
176
+ * @memberof Chartist.Core
177
+ * @param {Number} addend
178
+ * @returns {Function} Function that can be used in `Array.map` to add a addend to each value in an array
179
+ */
180
+ Chartist.mapAdd = function(addend) {
181
+ return function(num) {
182
+ return num + addend;
183
+ };
158
184
  };
159
185
 
160
186
  /**
@@ -282,7 +308,7 @@ var Chartist = {
282
308
  // Check if there is a previous SVG element in the container that contains the Chartist XML namespace and remove it
283
309
  // Since the DOM API does not support namespaces we need to manually search the returned list http://www.w3.org/TR/selectors-api/
284
310
  Array.prototype.slice.call(container.querySelectorAll('svg')).filter(function filterChartistSvgObjects(svg) {
285
- return svg.getAttribute(Chartist.xmlNs.qualifiedName);
311
+ return svg.getAttributeNS('http://www.w3.org/2000/xmlns/', Chartist.xmlNs.prefix);
286
312
  }).forEach(function removePreviousElement(svg) {
287
313
  container.removeChild(svg);
288
314
  });
@@ -314,7 +340,7 @@ var Chartist = {
314
340
  for (var i = 0; i < data.series.length; i++) {
315
341
  if(typeof(data.series[i]) === 'object' && data.series[i].data !== undefined) {
316
342
  data.series[i].data.reverse();
317
- } else {
343
+ } else if(data.series[i] instanceof Array) {
318
344
  data.series[i].reverse();
319
345
  }
320
346
  }
@@ -326,13 +352,10 @@ var Chartist = {
326
352
  * @memberof Chartist.Core
327
353
  * @param {Object} data The series object that contains the data to be visualized in the chart
328
354
  * @param {Boolean} reverse If true the whole data is reversed by the getDataArray call. This will modify the data object passed as first parameter. The labels as well as the series order is reversed. The whole series data arrays are reversed too.
355
+ * @param {Boolean} multi Create a multi dimensional array from a series data array where a value object with `x` and `y` values will be created.
329
356
  * @return {Array} A plain array that contains the data to be visualized in the chart
330
357
  */
331
- Chartist.getDataArray = function (data, reverse) {
332
- var array = [],
333
- value,
334
- localData;
335
-
358
+ Chartist.getDataArray = function (data, reverse, multi) {
336
359
  // If the data should be reversed but isn't we need to reverse it
337
360
  // If it's reversed but it shouldn't we need to reverse it back
338
361
  // That's required to handle data updates correctly and to reflect the responsive configurations
@@ -341,27 +364,41 @@ var Chartist = {
341
364
  data.reversed = !data.reversed;
342
365
  }
343
366
 
344
- for (var i = 0; i < data.series.length; i++) {
345
- // If the series array contains an object with a data property we will use the property
346
- // otherwise the value directly (array or number).
347
- // We create a copy of the original data array with Array.prototype.push.apply
348
- localData = typeof(data.series[i]) === 'object' && data.series[i].data !== undefined ? data.series[i].data : data.series[i];
349
- if(localData instanceof Array) {
350
- array[i] = [];
351
- Array.prototype.push.apply(array[i], localData);
367
+ // Recursively walks through nested arrays and convert string values to numbers and objects with value properties
368
+ // to values. Check the tests in data core -> data normalization for a detailed specification of expected values
369
+ function recursiveConvert(value) {
370
+ if(Chartist.isFalseyButZero(value)) {
371
+ // This is a hole in data and we should return undefined
372
+ return undefined;
373
+ } else if((value.data || value) instanceof Array) {
374
+ return (value.data || value).map(recursiveConvert);
375
+ } else if(value.hasOwnProperty('value')) {
376
+ return recursiveConvert(value.value);
352
377
  } else {
353
- array[i] = localData;
354
- }
378
+ if(multi) {
379
+ var multiValue = {};
380
+
381
+ // Single series value arrays are assumed to specify the Y-Axis value
382
+ // For example: [1, 2] => [{x: undefined, y: 1}, {x: undefined, y: 2}]
383
+ // If multi is a string then it's assumed that it specified which dimension should be filled as default
384
+ if(typeof multi === 'string') {
385
+ multiValue[multi] = Chartist.getNumberOrUndefined(value);
386
+ } else {
387
+ multiValue.y = Chartist.getNumberOrUndefined(value);
388
+ }
355
389
 
356
- // Convert object values to numbers
357
- for (var j = 0; j < array[i].length; j++) {
358
- value = array[i][j];
359
- value = value.value === 0 ? 0 : (value.value || value);
360
- array[i][j] = +value;
390
+ multiValue.x = value.hasOwnProperty('x') ? Chartist.getNumberOrUndefined(value.x) : multiValue.x;
391
+ multiValue.y = value.hasOwnProperty('y') ? Chartist.getNumberOrUndefined(value.y) : multiValue.y;
392
+
393
+ return multiValue;
394
+
395
+ } else {
396
+ return Chartist.getNumberOrUndefined(value);
397
+ }
361
398
  }
362
399
  }
363
400
 
364
- return array;
401
+ return data.series.map(recursiveConvert);
365
402
  };
366
403
 
367
404
  /**
@@ -388,28 +425,6 @@ var Chartist = {
388
425
  };
389
426
  };
390
427
 
391
- /**
392
- * Adds missing values at the end of the array. This array contains the data, that will be visualized in the chart
393
- *
394
- * @memberof Chartist.Core
395
- * @param {Array} dataArray The array that contains the data to be visualized in the chart. The array in this parameter will be modified by function.
396
- * @param {Number} length The length of the x-axis data array.
397
- * @return {Array} The array that got updated with missing values.
398
- */
399
- Chartist.normalizeDataArray = function (dataArray, length) {
400
- for (var i = 0; i < dataArray.length; i++) {
401
- if (dataArray[i].length === length) {
402
- continue;
403
- }
404
-
405
- for (var j = dataArray[i].length; j < length; j++) {
406
- dataArray[i][j] = 0;
407
- }
408
- }
409
-
410
- return dataArray;
411
- };
412
-
413
428
  Chartist.getMetaData = function(series, index) {
414
429
  var value = series.data ? series.data[index] : series[index];
415
430
  return value ? Chartist.serialize(value.meta) : undefined;
@@ -455,32 +470,162 @@ var Chartist = {
455
470
  * Get highest and lowest value of data array. This Array contains the data that will be visualized in the chart.
456
471
  *
457
472
  * @memberof Chartist.Core
458
- * @param {Array} dataArray The array that contains the data to be visualized in the chart
473
+ * @param {Array} data The array that contains the data to be visualized in the chart
474
+ * @param {Object} options The Object that contains the chart options
475
+ * @param {String} dimension Axis dimension 'x' or 'y' used to access the correct value and high / low configuration
459
476
  * @return {Object} An object that contains the highest and lowest value that will be visualized on the chart.
460
477
  */
461
- Chartist.getHighLow = function (dataArray) {
462
- var i,
463
- j,
464
- highLow = {
465
- high: -Number.MAX_VALUE,
466
- low: Number.MAX_VALUE
478
+ Chartist.getHighLow = function (data, options, dimension) {
479
+ // TODO: Remove workaround for deprecated global high / low config. Axis high / low configuration is preferred
480
+ options = Chartist.extend({}, options, dimension ? options['axis' + dimension.toUpperCase()] : {});
481
+
482
+ var highLow = {
483
+ high: options.high === undefined ? -Number.MAX_VALUE : +options.high,
484
+ low: options.low === undefined ? Number.MAX_VALUE : +options.low
467
485
  };
486
+ var findHigh = options.high === undefined;
487
+ var findLow = options.low === undefined;
488
+
489
+ // Function to recursively walk through arrays and find highest and lowest number
490
+ function recursiveHighLow(data) {
491
+ if(data === undefined) {
492
+ return undefined;
493
+ } else if(data instanceof Array) {
494
+ for (var i = 0; i < data.length; i++) {
495
+ recursiveHighLow(data[i]);
496
+ }
497
+ } else {
498
+ var value = dimension ? +data[dimension] : +data;
468
499
 
469
- for (i = 0; i < dataArray.length; i++) {
470
- for (j = 0; j < dataArray[i].length; j++) {
471
- if (dataArray[i][j] > highLow.high) {
472
- highLow.high = dataArray[i][j];
500
+ if (findHigh && value > highLow.high) {
501
+ highLow.high = value;
473
502
  }
474
503
 
475
- if (dataArray[i][j] < highLow.low) {
476
- highLow.low = dataArray[i][j];
504
+ if (findLow && value < highLow.low) {
505
+ highLow.low = value;
477
506
  }
478
507
  }
479
508
  }
480
509
 
510
+ // Start to find highest and lowest number recursively
511
+ if(findHigh || findLow) {
512
+ recursiveHighLow(data);
513
+ }
514
+
515
+ // Overrides of high / low based on reference value, it will make sure that the invisible reference value is
516
+ // used to generate the chart. This is useful when the chart always needs to contain the position of the
517
+ // invisible reference value in the view i.e. for bipolar scales.
518
+ if (options.referenceValue || options.referenceValue === 0) {
519
+ highLow.high = Math.max(options.referenceValue, highLow.high);
520
+ highLow.low = Math.min(options.referenceValue, highLow.low);
521
+ }
522
+
523
+ // If high and low are the same because of misconfiguration or flat data (only the same value) we need
524
+ // to set the high or low to 0 depending on the polarity
525
+ if (highLow.high <= highLow.low) {
526
+ // If both values are 0 we set high to 1
527
+ if (highLow.low === 0) {
528
+ highLow.high = 1;
529
+ } else if (highLow.low < 0) {
530
+ // If we have the same negative value for the bounds we set bounds.high to 0
531
+ highLow.high = 0;
532
+ } else {
533
+ // If we have the same positive value for the bounds we set bounds.low to 0
534
+ highLow.low = 0;
535
+ }
536
+ }
537
+
481
538
  return highLow;
482
539
  };
483
540
 
541
+ /**
542
+ * Checks if the value is a valid number or string with a number.
543
+ *
544
+ * @memberof Chartist.Core
545
+ * @param value
546
+ * @returns {Boolean}
547
+ */
548
+ Chartist.isNum = function(value) {
549
+ return !isNaN(value) && isFinite(value);
550
+ };
551
+
552
+ /**
553
+ * Returns true on all falsey values except the numeric value 0.
554
+ *
555
+ * @memberof Chartist.Core
556
+ * @param value
557
+ * @returns {boolean}
558
+ */
559
+ Chartist.isFalseyButZero = function(value) {
560
+ return !value && value !== 0;
561
+ };
562
+
563
+ /**
564
+ * Returns a number if the passed parameter is a valid number or the function will return undefined. On all other values than a valid number, this function will return undefined.
565
+ *
566
+ * @memberof Chartist.Core
567
+ * @param value
568
+ * @returns {*}
569
+ */
570
+ Chartist.getNumberOrUndefined = function(value) {
571
+ return isNaN(+value) ? undefined : +value;
572
+ };
573
+
574
+ /**
575
+ * Gets a value from a dimension `value.x` or `value.y` while returning value directly if it's a valid numeric value. If the value is not numeric and it's falsey this function will return undefined.
576
+ *
577
+ * @param value
578
+ * @param dimension
579
+ * @returns {*}
580
+ */
581
+ Chartist.getMultiValue = function(value, dimension) {
582
+ if(Chartist.isNum(value)) {
583
+ return +value;
584
+ } else if(value) {
585
+ return value[dimension || 'y'] || 0;
586
+ } else {
587
+ return 0;
588
+ }
589
+ };
590
+
591
+ /**
592
+ * Pollard Rho Algorithm to find smallest factor of an integer value. There are more efficient algorithms for factorization, but this one is quite efficient and not so complex.
593
+ *
594
+ * @memberof Chartist.Core
595
+ * @param {Number} num An integer number where the smallest factor should be searched for
596
+ * @returns {Number} The smallest integer factor of the parameter num.
597
+ */
598
+ Chartist.rho = function(num) {
599
+ if(num === 1) {
600
+ return num;
601
+ }
602
+
603
+ function gcd(p, q) {
604
+ if (p % q === 0) {
605
+ return q;
606
+ } else {
607
+ return gcd(q, p % q);
608
+ }
609
+ }
610
+
611
+ function f(x) {
612
+ return x * x + 1;
613
+ }
614
+
615
+ var x1 = 2, x2 = 2, divisor;
616
+ if (num % 2 === 0) {
617
+ return 2;
618
+ }
619
+
620
+ do {
621
+ x1 = f(x1) % num;
622
+ x2 = f(f(x2)) % num;
623
+ divisor = gcd(Math.abs(x1 - x2), num);
624
+ } while (divisor === 1);
625
+
626
+ return divisor;
627
+ };
628
+
484
629
  /**
485
630
  * Calculate and retrieve all the bounds for the chart and return them in one array
486
631
  *
@@ -488,11 +633,12 @@ var Chartist = {
488
633
  * @param {Number} axisLength The length of the Axis used for
489
634
  * @param {Object} highLow An object containing a high and low property indicating the value range of the chart.
490
635
  * @param {Number} scaleMinSpace The minimum projected length a step should result in
491
- * @param {Number} referenceValue The reference value for the chart.
636
+ * @param {Boolean} onlyInteger
492
637
  * @return {Object} All the values to set the bounds of the chart
493
638
  */
494
- Chartist.getBounds = function (axisLength, highLow, scaleMinSpace, referenceValue) {
639
+ Chartist.getBounds = function (axisLength, highLow, scaleMinSpace, onlyInteger) {
495
640
  var i,
641
+ optimizationCounter = 0,
496
642
  newMin,
497
643
  newMax,
498
644
  bounds = {
@@ -500,63 +646,57 @@ var Chartist = {
500
646
  low: highLow.low
501
647
  };
502
648
 
503
- // If high and low are the same because of misconfiguration or flat data (only the same value) we need
504
- // to set the high or low to 0 depending on the polarity
505
- if(bounds.high === bounds.low) {
506
- // If both values are 0 we set high to 1
507
- if(bounds.low === 0) {
508
- bounds.high = 1;
509
- } else if(bounds.low < 0) {
510
- // If we have the same negative value for the bounds we set bounds.high to 0
511
- bounds.high = 0;
512
- } else {
513
- // If we have the same positive value for the bounds we set bounds.low to 0
514
- bounds.low = 0;
515
- }
516
- }
517
-
518
- // Overrides of high / low based on reference value, it will make sure that the invisible reference value is
519
- // used to generate the chart. This is useful when the chart always needs to contain the position of the
520
- // invisible reference value in the view i.e. for bipolar scales.
521
- if (referenceValue || referenceValue === 0) {
522
- bounds.high = Math.max(referenceValue, bounds.high);
523
- bounds.low = Math.min(referenceValue, bounds.low);
524
- }
525
-
526
649
  bounds.valueRange = bounds.high - bounds.low;
527
650
  bounds.oom = Chartist.orderOfMagnitude(bounds.valueRange);
528
- bounds.min = Math.floor(bounds.low / Math.pow(10, bounds.oom)) * Math.pow(10, bounds.oom);
529
- bounds.max = Math.ceil(bounds.high / Math.pow(10, bounds.oom)) * Math.pow(10, bounds.oom);
530
- bounds.range = bounds.max - bounds.min;
531
651
  bounds.step = Math.pow(10, bounds.oom);
652
+ bounds.min = Math.floor(bounds.low / bounds.step) * bounds.step;
653
+ bounds.max = Math.ceil(bounds.high / bounds.step) * bounds.step;
654
+ bounds.range = bounds.max - bounds.min;
532
655
  bounds.numberOfSteps = Math.round(bounds.range / bounds.step);
533
656
 
534
657
  // Optimize scale step by checking if subdivision is possible based on horizontalGridMinSpace
535
658
  // If we are already below the scaleMinSpace value we will scale up
536
- var length = Chartist.projectLength(axisLength, bounds.step, bounds),
537
- scaleUp = length < scaleMinSpace;
538
-
539
- while (true) {
540
- if (scaleUp && Chartist.projectLength(axisLength, bounds.step, bounds) <= scaleMinSpace) {
541
- bounds.step *= 2;
542
- } else if (!scaleUp && Chartist.projectLength(axisLength, bounds.step / 2, bounds) >= scaleMinSpace) {
543
- bounds.step /= 2;
544
- } else {
545
- break;
659
+ var length = Chartist.projectLength(axisLength, bounds.step, bounds);
660
+ var scaleUp = length < scaleMinSpace;
661
+ var smallestFactor = onlyInteger ? Chartist.rho(bounds.range) : 0;
662
+
663
+ // First check if we should only use integer steps and if step 1 is still larger than scaleMinSpace so we can use 1
664
+ if(onlyInteger && Chartist.projectLength(axisLength, 1, bounds) >= scaleMinSpace) {
665
+ bounds.step = 1;
666
+ } else if(onlyInteger && smallestFactor < bounds.step && Chartist.projectLength(axisLength, smallestFactor, bounds) >= scaleMinSpace) {
667
+ // If step 1 was too small, we can try the smallest factor of range
668
+ // If the smallest factor is smaller than the current bounds.step and the projected length of smallest factor
669
+ // is larger than the scaleMinSpace we should go for it.
670
+ bounds.step = smallestFactor;
671
+ } else {
672
+ // Trying to divide or multiply by 2 and find the best step value
673
+ while (true) {
674
+ if (scaleUp && Chartist.projectLength(axisLength, bounds.step, bounds) <= scaleMinSpace) {
675
+ bounds.step *= 2;
676
+ } else if (!scaleUp && Chartist.projectLength(axisLength, bounds.step / 2, bounds) >= scaleMinSpace) {
677
+ bounds.step /= 2;
678
+ if(onlyInteger && bounds.step % 1 !== 0) {
679
+ bounds.step *= 2;
680
+ break;
681
+ }
682
+ } else {
683
+ break;
684
+ }
685
+
686
+ if(optimizationCounter++ > 1000) {
687
+ throw new Error('Exceeded maximum number of iterations while optimizing scale step!');
688
+ }
546
689
  }
547
690
  }
548
691
 
549
692
  // Narrow min and max based on new step
550
693
  newMin = bounds.min;
551
694
  newMax = bounds.max;
552
- for (i = bounds.min; i <= bounds.max; i += bounds.step) {
553
- if (i + bounds.step < bounds.low) {
554
- newMin += bounds.step;
555
- }
556
-
557
- if (i - bounds.step >= bounds.high) {
558
- newMax -= bounds.step;
559
- }
695
+ while(newMin + bounds.step <= bounds.low) {
696
+ newMin += bounds.step;
697
+ }
698
+ while(newMax - bounds.step >= bounds.high) {
699
+ newMax -= bounds.step;
560
700
  }
561
701
  bounds.min = newMin;
562
702
  bounds.max = newMax;
@@ -599,17 +739,20 @@ var Chartist = {
599
739
  * @return {Object} The chart rectangles coordinates inside the svg element plus the rectangles measurements
600
740
  */
601
741
  Chartist.createChartRect = function (svg, options, fallbackPadding) {
602
- var yOffset = options.axisY ? options.axisY.offset || 0 : 0,
603
- xOffset = options.axisX ? options.axisX.offset || 0 : 0,
604
- w = Chartist.stripUnit(options.width) || svg.width(),
605
- h = Chartist.stripUnit(options.height) || svg.height(),
606
- normalizedPadding = Chartist.normalizePadding(options.chartPadding, fallbackPadding);
607
-
608
- return {
609
- x1: normalizedPadding.left + yOffset,
610
- y1: Math.max(h - normalizedPadding.bottom - xOffset, normalizedPadding.bottom),
611
- x2: Math.max(w - normalizedPadding.right, normalizedPadding.right + yOffset),
612
- y2: normalizedPadding.top,
742
+ var hasAxis = !!(options.axisX || options.axisY);
743
+ var yAxisOffset = hasAxis ? options.axisY.offset : 0;
744
+ var xAxisOffset = hasAxis ? options.axisX.offset : 0;
745
+ // If width or height results in invalid value (including 0) we fallback to the unitless settings or even 0
746
+ var width = svg.width() || Chartist.stripUnit(options.width) || 0;
747
+ var height = svg.height() || Chartist.stripUnit(options.height) || 0;
748
+ var normalizedPadding = Chartist.normalizePadding(options.chartPadding, fallbackPadding);
749
+
750
+ // If settings were to small to cope with offset (legacy) and padding, we'll adjust
751
+ width = Math.max(width, yAxisOffset + normalizedPadding.left + normalizedPadding.right);
752
+ height = Math.max(height, xAxisOffset + normalizedPadding.top + normalizedPadding.bottom);
753
+
754
+ var chartRect = {
755
+ padding: normalizedPadding,
613
756
  width: function () {
614
757
  return this.x2 - this.x1;
615
758
  },
@@ -617,13 +760,38 @@ var Chartist = {
617
760
  return this.y1 - this.y2;
618
761
  }
619
762
  };
763
+
764
+ if(hasAxis) {
765
+ if (options.axisX.position === 'start') {
766
+ chartRect.y2 = normalizedPadding.top + xAxisOffset;
767
+ chartRect.y1 = Math.max(height - normalizedPadding.bottom, chartRect.y2 + 1);
768
+ } else {
769
+ chartRect.y2 = normalizedPadding.top;
770
+ chartRect.y1 = Math.max(height - normalizedPadding.bottom - xAxisOffset, chartRect.y2 + 1);
771
+ }
772
+
773
+ if (options.axisY.position === 'start') {
774
+ chartRect.x1 = normalizedPadding.left + yAxisOffset;
775
+ chartRect.x2 = Math.max(width - normalizedPadding.right, chartRect.x1 + 1);
776
+ } else {
777
+ chartRect.x1 = normalizedPadding.left;
778
+ chartRect.x2 = Math.max(width - normalizedPadding.right - yAxisOffset, chartRect.x1 + 1);
779
+ }
780
+ } else {
781
+ chartRect.x1 = normalizedPadding.left;
782
+ chartRect.x2 = Math.max(width - normalizedPadding.right, chartRect.x1 + 1);
783
+ chartRect.y2 = normalizedPadding.top;
784
+ chartRect.y1 = Math.max(height - normalizedPadding.bottom, chartRect.y2 + 1);
785
+ }
786
+
787
+ return chartRect;
620
788
  };
621
789
 
622
790
  /**
623
791
  * Creates a grid line based on a projected value.
624
792
  *
625
793
  * @memberof Chartist.Core
626
- * @param projectedValue
794
+ * @param position
627
795
  * @param index
628
796
  * @param axis
629
797
  * @param offset
@@ -632,10 +800,10 @@ var Chartist = {
632
800
  * @param classes
633
801
  * @param eventEmitter
634
802
  */
635
- Chartist.createGrid = function(projectedValue, index, axis, offset, length, group, classes, eventEmitter) {
803
+ Chartist.createGrid = function(position, index, axis, offset, length, group, classes, eventEmitter) {
636
804
  var positionalData = {};
637
- positionalData[axis.units.pos + '1'] = projectedValue.pos;
638
- positionalData[axis.units.pos + '2'] = projectedValue.pos;
805
+ positionalData[axis.units.pos + '1'] = position;
806
+ positionalData[axis.units.pos + '2'] = position;
639
807
  positionalData[axis.counterUnits.pos + '1'] = offset;
640
808
  positionalData[axis.counterUnits.pos + '2'] = offset + length;
641
809
 
@@ -657,7 +825,8 @@ var Chartist = {
657
825
  * Creates a label based on a projected value and an axis.
658
826
  *
659
827
  * @memberof Chartist.Core
660
- * @param projectedValue
828
+ * @param position
829
+ * @param length
661
830
  * @param index
662
831
  * @param labels
663
832
  * @param axis
@@ -668,16 +837,23 @@ var Chartist = {
668
837
  * @param useForeignObject
669
838
  * @param eventEmitter
670
839
  */
671
- Chartist.createLabel = function(projectedValue, index, labels, axis, axisOffset, labelOffset, group, classes, useForeignObject, eventEmitter) {
672
- var labelElement,
673
- positionalData = {};
674
- positionalData[axis.units.pos] = projectedValue.pos + labelOffset[axis.units.pos];
840
+ Chartist.createLabel = function(position, length, index, labels, axis, axisOffset, labelOffset, group, classes, useForeignObject, eventEmitter) {
841
+ var labelElement;
842
+ var positionalData = {};
843
+
844
+ positionalData[axis.units.pos] = position + labelOffset[axis.units.pos];
675
845
  positionalData[axis.counterUnits.pos] = labelOffset[axis.counterUnits.pos];
676
- positionalData[axis.units.len] = projectedValue.len;
677
- positionalData[axis.counterUnits.len] = axisOffset;
846
+ positionalData[axis.units.len] = length;
847
+ positionalData[axis.counterUnits.len] = axisOffset - 10;
678
848
 
679
849
  if(useForeignObject) {
680
- var content = '<span class="' + classes.join(' ') + '">' + labels[index] + '</span>';
850
+ // We need to set width and height explicitly to px as span will not expand with width and height being
851
+ // 100% in all browsers
852
+ var content = '<span class="' + classes.join(' ') + '" style="' +
853
+ axis.units.len + ': ' + Math.round(positionalData[axis.units.len]) + 'px; ' +
854
+ axis.counterUnits.len + ': ' + Math.round(positionalData[axis.counterUnits.len]) + 'px">' +
855
+ labels[index] + '</span>';
856
+
681
857
  labelElement = group.foreignObject(content, Chartist.extend({
682
858
  style: 'overflow: visible;'
683
859
  }, positionalData));
@@ -696,43 +872,21 @@ var Chartist = {
696
872
  };
697
873
 
698
874
  /**
699
- * This function creates a whole axis with its grid lines and labels based on an axis model and a chartRect.
875
+ * Helper to read series specific options from options object. It automatically falls back to the global option if
876
+ * there is no option in the series options.
700
877
  *
701
- * @memberof Chartist.Core
702
- * @param axis
703
- * @param data
704
- * @param chartRect
705
- * @param gridGroup
706
- * @param labelGroup
707
- * @param useForeignObject
708
- * @param options
709
- * @param eventEmitter
878
+ * @param {Object} series Series object
879
+ * @param {Object} options Chartist options object
880
+ * @param {string} key The options key that should be used to obtain the options
881
+ * @returns {*}
710
882
  */
711
- Chartist.createAxis = function(axis, data, chartRect, gridGroup, labelGroup, useForeignObject, options, eventEmitter) {
712
- var axisOptions = options['axis' + axis.units.pos.toUpperCase()],
713
- projectedValues = data.map(axis.projectValue.bind(axis)).map(axis.transform),
714
- labelValues = data.map(axisOptions.labelInterpolationFnc);
715
-
716
- projectedValues.forEach(function(projectedValue, index) {
717
- // Skip grid lines and labels where interpolated label values are falsey (execpt for 0)
718
- if(!labelValues[index] && labelValues[index] !== 0) {
719
- return;
720
- }
721
-
722
- if(axisOptions.showGrid) {
723
- Chartist.createGrid(projectedValue, index, axis, axis.gridOffset, chartRect[axis.counterUnits.len](), gridGroup, [
724
- options.classNames.grid,
725
- options.classNames[axis.units.dir]
726
- ], eventEmitter);
727
- }
728
-
729
- if(axisOptions.showLabel) {
730
- Chartist.createLabel(projectedValue, index, labelValues, axis, axisOptions.offset, axis.labelOffset, labelGroup, [
731
- options.classNames.label,
732
- options.classNames[axis.units.dir]
733
- ], useForeignObject, eventEmitter);
734
- }
735
- });
883
+ Chartist.getSeriesOption = function(series, options, key) {
884
+ if(series.name && options.series && options.series[series.name]) {
885
+ var seriesOptions = options.series[series.name];
886
+ return seriesOptions.hasOwnProperty(key) ? seriesOptions[key] : options[key];
887
+ } else {
888
+ return options[key];
889
+ }
736
890
  };
737
891
 
738
892
  /**
@@ -791,10 +945,10 @@ var Chartist = {
791
945
  updateCurrentOptions(true);
792
946
 
793
947
  return {
794
- get currentOptions() {
948
+ removeMediaQueryListeners: removeMediaQueryListeners,
949
+ getCurrentOptions: function getCurrentOptions() {
795
950
  return Chartist.extend({}, currentOptions);
796
- },
797
- removeMediaQueryListeners: removeMediaQueryListeners
951
+ }
798
952
  };
799
953
  };
800
954
 
@@ -817,11 +971,27 @@ var Chartist = {
817
971
  * @return {Function}
818
972
  */
819
973
  Chartist.Interpolation.none = function() {
820
- return function cardinal(pathCoordinates) {
821
- var path = new Chartist.Svg.Path().move(pathCoordinates[0], pathCoordinates[1]);
974
+ return function none(pathCoordinates, valueData) {
975
+ var path = new Chartist.Svg.Path();
976
+ // We need to assume that the first value is a "hole"
977
+ var hole = true;
822
978
 
823
- for(var i = 3; i < pathCoordinates.length; i += 2) {
824
- path.line(pathCoordinates[i - 1], pathCoordinates[i]);
979
+ for(var i = 1; i < pathCoordinates.length; i += 2) {
980
+ var data = valueData[(i - 1) / 2];
981
+
982
+ // If the current value is undefined we should treat it as a hole start
983
+ if(data.value === undefined) {
984
+ hole = true;
985
+ } else {
986
+ // If this value is valid we need to check if we're coming out of a hole
987
+ if(hole) {
988
+ // If we are coming out of a hole we should first make a move and also reset the hole flag
989
+ path.move(pathCoordinates[i - 1], pathCoordinates[i], false, data);
990
+ hole = false;
991
+ } else {
992
+ path.line(pathCoordinates[i - 1], pathCoordinates[i], false, data);
993
+ }
994
+ }
825
995
  }
826
996
 
827
997
  return path;
@@ -858,24 +1028,42 @@ var Chartist = {
858
1028
 
859
1029
  var d = 1 / Math.max(1, options.divisor);
860
1030
 
861
- return function simple(pathCoordinates) {
862
- var path = new Chartist.Svg.Path().move(pathCoordinates[0], pathCoordinates[1]);
1031
+ return function simple(pathCoordinates, valueData) {
1032
+ var path = new Chartist.Svg.Path();
1033
+ var hole = true;
863
1034
 
864
1035
  for(var i = 2; i < pathCoordinates.length; i += 2) {
865
- var prevX = pathCoordinates[i - 2],
866
- prevY = pathCoordinates[i - 1],
867
- currX = pathCoordinates[i],
868
- currY = pathCoordinates[i + 1],
869
- length = (currX - prevX) * d;
870
-
871
- path.curve(
872
- prevX + length,
873
- prevY,
874
- currX - length,
875
- currY,
876
- currX,
877
- currY
878
- );
1036
+ var prevX = pathCoordinates[i - 2];
1037
+ var prevY = pathCoordinates[i - 1];
1038
+ var currX = pathCoordinates[i];
1039
+ var currY = pathCoordinates[i + 1];
1040
+ var length = (currX - prevX) * d;
1041
+ var prevData = valueData[(i / 2) - 1];
1042
+ var currData = valueData[i / 2];
1043
+
1044
+ if(prevData.value === undefined) {
1045
+ hole = true;
1046
+ } else {
1047
+
1048
+ if(hole) {
1049
+ path.move(prevX, prevY, false, prevData);
1050
+ }
1051
+
1052
+ if(currData.value !== undefined) {
1053
+ path.curve(
1054
+ prevX + length,
1055
+ prevY,
1056
+ currX - length,
1057
+ currY,
1058
+ currX,
1059
+ currY,
1060
+ false,
1061
+ currData
1062
+ );
1063
+
1064
+ hole = false;
1065
+ }
1066
+ }
879
1067
  }
880
1068
 
881
1069
  return path;
@@ -913,47 +1101,169 @@ var Chartist = {
913
1101
  var t = Math.min(1, Math.max(0, options.tension)),
914
1102
  c = 1 - t;
915
1103
 
916
- return function cardinal(pathCoordinates) {
917
- // If less than two points we need to fallback to no smoothing
918
- if(pathCoordinates.length <= 4) {
919
- return Chartist.Interpolation.none()(pathCoordinates);
1104
+ // This function will help us to split pathCoordinates and valueData into segments that also contain pathCoordinates
1105
+ // and valueData. This way the existing functions can be reused and the segment paths can be joined afterwards.
1106
+ // This functionality is necessary to treat "holes" in the line charts
1107
+ function splitIntoSegments(pathCoordinates, valueData) {
1108
+ var segments = [];
1109
+ var hole = true;
1110
+
1111
+ for(var i = 0; i < pathCoordinates.length; i += 2) {
1112
+ // If this value is a "hole" we set the hole flag
1113
+ if(valueData[i / 2].value === undefined) {
1114
+ hole = true;
1115
+ } else {
1116
+ // If it's a valid value we need to check if we're coming out of a hole and create a new empty segment
1117
+ if(hole) {
1118
+ segments.push({
1119
+ pathCoordinates: [],
1120
+ valueData: []
1121
+ });
1122
+ // As we have a valid value now, we are not in a "hole" anymore
1123
+ hole = false;
1124
+ }
1125
+
1126
+ // Add to the segment pathCoordinates and valueData
1127
+ segments[segments.length - 1].pathCoordinates.push(pathCoordinates[i], pathCoordinates[i + 1]);
1128
+ segments[segments.length - 1].valueData.push(valueData[i / 2]);
1129
+ }
920
1130
  }
921
1131
 
922
- var path = new Chartist.Svg.Path().move(pathCoordinates[0], pathCoordinates[1]),
923
- z;
924
-
925
- for (var i = 0, iLen = pathCoordinates.length; iLen - 2 * !z > i; i += 2) {
926
- var p = [
927
- {x: +pathCoordinates[i - 2], y: +pathCoordinates[i - 1]},
928
- {x: +pathCoordinates[i], y: +pathCoordinates[i + 1]},
929
- {x: +pathCoordinates[i + 2], y: +pathCoordinates[i + 3]},
930
- {x: +pathCoordinates[i + 4], y: +pathCoordinates[i + 5]}
931
- ];
932
- if (z) {
933
- if (!i) {
934
- p[0] = {x: +pathCoordinates[iLen - 2], y: +pathCoordinates[iLen - 1]};
935
- } else if (iLen - 4 === i) {
936
- p[3] = {x: +pathCoordinates[0], y: +pathCoordinates[1]};
937
- } else if (iLen - 2 === i) {
938
- p[2] = {x: +pathCoordinates[0], y: +pathCoordinates[1]};
939
- p[3] = {x: +pathCoordinates[2], y: +pathCoordinates[3]};
1132
+ return segments;
1133
+ }
1134
+
1135
+ return function cardinal(pathCoordinates, valueData) {
1136
+ // First we try to split the coordinates into segments
1137
+ // This is necessary to treat "holes" in line charts
1138
+ var segments = splitIntoSegments(pathCoordinates, valueData);
1139
+
1140
+ // If the split resulted in more that one segment we need to interpolate each segment individually and join them
1141
+ // afterwards together into a single path.
1142
+ if(segments.length > 1) {
1143
+ var paths = [];
1144
+ // For each segment we will recurse the cardinal function
1145
+ segments.forEach(function(segment) {
1146
+ paths.push(cardinal(segment.pathCoordinates, segment.valueData));
1147
+ });
1148
+ // Join the segment path data into a single path and return
1149
+ return Chartist.Svg.Path.join(paths);
1150
+ } else {
1151
+ // If there was only one segment we can proceed regularly by using pathCoordinates and valueData from the first
1152
+ // segment
1153
+ pathCoordinates = segments[0].pathCoordinates;
1154
+ valueData = segments[0].valueData;
1155
+
1156
+ // If less than two points we need to fallback to no smoothing
1157
+ if(pathCoordinates.length <= 4) {
1158
+ return Chartist.Interpolation.none()(pathCoordinates, valueData);
1159
+ }
1160
+
1161
+ var path = new Chartist.Svg.Path().move(pathCoordinates[0], pathCoordinates[1], false, valueData[0]),
1162
+ z;
1163
+
1164
+ for (var i = 0, iLen = pathCoordinates.length; iLen - 2 * !z > i; i += 2) {
1165
+ var p = [
1166
+ {x: +pathCoordinates[i - 2], y: +pathCoordinates[i - 1]},
1167
+ {x: +pathCoordinates[i], y: +pathCoordinates[i + 1]},
1168
+ {x: +pathCoordinates[i + 2], y: +pathCoordinates[i + 3]},
1169
+ {x: +pathCoordinates[i + 4], y: +pathCoordinates[i + 5]}
1170
+ ];
1171
+ if (z) {
1172
+ if (!i) {
1173
+ p[0] = {x: +pathCoordinates[iLen - 2], y: +pathCoordinates[iLen - 1]};
1174
+ } else if (iLen - 4 === i) {
1175
+ p[3] = {x: +pathCoordinates[0], y: +pathCoordinates[1]};
1176
+ } else if (iLen - 2 === i) {
1177
+ p[2] = {x: +pathCoordinates[0], y: +pathCoordinates[1]};
1178
+ p[3] = {x: +pathCoordinates[2], y: +pathCoordinates[3]};
1179
+ }
1180
+ } else {
1181
+ if (iLen - 4 === i) {
1182
+ p[3] = p[2];
1183
+ } else if (!i) {
1184
+ p[0] = {x: +pathCoordinates[i], y: +pathCoordinates[i + 1]};
1185
+ }
940
1186
  }
1187
+
1188
+ path.curve(
1189
+ (t * (-p[0].x + 6 * p[1].x + p[2].x) / 6) + (c * p[2].x),
1190
+ (t * (-p[0].y + 6 * p[1].y + p[2].y) / 6) + (c * p[2].y),
1191
+ (t * (p[1].x + 6 * p[2].x - p[3].x) / 6) + (c * p[2].x),
1192
+ (t * (p[1].y + 6 * p[2].y - p[3].y) / 6) + (c * p[2].y),
1193
+ p[2].x,
1194
+ p[2].y,
1195
+ false,
1196
+ valueData[(i + 2) / 2]
1197
+ );
1198
+ }
1199
+
1200
+ return path;
1201
+ }
1202
+ };
1203
+ };
1204
+
1205
+ /**
1206
+ * Step interpolation will cause the line chart to move in steps rather than diagonal or smoothed lines. This interpolation will create additional points that will also be drawn when the `showPoint` option is enabled.
1207
+ *
1208
+ * All smoothing functions within Chartist are factory functions that accept an options parameter. The step interpolation function accepts one configuration parameter `postpone`, that can be `true` or `false`. The default value is `true` and will cause the step to occur where the value actually changes. If a different behaviour is needed where the step is shifted to the left and happens before the actual value, this option can be set to `false`.
1209
+ *
1210
+ * @example
1211
+ * var chart = new Chartist.Line('.ct-chart', {
1212
+ * labels: [1, 2, 3, 4, 5],
1213
+ * series: [[1, 2, 8, 1, 7]]
1214
+ * }, {
1215
+ * lineSmooth: Chartist.Interpolation.step({
1216
+ * postpone: true
1217
+ * })
1218
+ * });
1219
+ *
1220
+ * @memberof Chartist.Interpolation
1221
+ * @param options
1222
+ * @returns {Function}
1223
+ */
1224
+ Chartist.Interpolation.step = function(options) {
1225
+ var defaultOptions = {
1226
+ postpone: true
1227
+ };
1228
+
1229
+ options = Chartist.extend({}, defaultOptions, options);
1230
+
1231
+ return function step(pathCoordinates, valueData) {
1232
+ var path = new Chartist.Svg.Path();
1233
+ var hole = true;
1234
+
1235
+ for (var i = 2; i < pathCoordinates.length; i += 2) {
1236
+ var prevX = pathCoordinates[i - 2];
1237
+ var prevY = pathCoordinates[i - 1];
1238
+ var currX = pathCoordinates[i];
1239
+ var currY = pathCoordinates[i + 1];
1240
+ var prevData = valueData[(i / 2) - 1];
1241
+ var currData = valueData[i / 2];
1242
+
1243
+ // If last point is a "hole"
1244
+ if(prevData.value === undefined) {
1245
+ hole = true;
941
1246
  } else {
942
- if (iLen - 4 === i) {
943
- p[3] = p[2];
944
- } else if (!i) {
945
- p[0] = {x: +pathCoordinates[i], y: +pathCoordinates[i + 1]};
1247
+ // If last point is not a "hole" but we just came back out of a "hole" we need to move first
1248
+ if(hole) {
1249
+ path.move(prevX, prevY, false, prevData);
946
1250
  }
947
- }
948
1251
 
949
- path.curve(
950
- (t * (-p[0].x + 6 * p[1].x + p[2].x) / 6) + (c * p[2].x),
951
- (t * (-p[0].y + 6 * p[1].y + p[2].y) / 6) + (c * p[2].y),
952
- (t * (p[1].x + 6 * p[2].x - p[3].x) / 6) + (c * p[2].x),
953
- (t * (p[1].y + 6 * p[2].y - p[3].y) / 6) + (c * p[2].y),
954
- p[2].x,
955
- p[2].y
956
- );
1252
+ // If the current point is also not a hole we can draw the step lines
1253
+ if(currData.value !== undefined) {
1254
+ if(options.postpone) {
1255
+ // If postponed we should draw the step line with the value of the previous value
1256
+ path.line(currX, prevY, false, prevData);
1257
+ } else {
1258
+ // If not postponed we should draw the step line with the value of the current value
1259
+ path.line(prevX, currY, false, currData);
1260
+ }
1261
+ // Line to the actual point (this should only be a Y-Axis movement
1262
+ path.line(currX, currY, false, currData);
1263
+ // Reset the "hole" flag as previous and current point have valid values
1264
+ hole = false;
1265
+ }
1266
+ }
957
1267
  }
958
1268
 
959
1269
  return path;
@@ -1196,7 +1506,7 @@ var Chartist = {
1196
1506
 
1197
1507
  // Only re-created the chart if it has been initialized yet
1198
1508
  if(!this.initializeTimeoutId) {
1199
- this.createChart(this.optionsProvider.currentOptions);
1509
+ this.createChart(this.optionsProvider.getCurrentOptions());
1200
1510
  }
1201
1511
 
1202
1512
  // Return a reference to the chart object to chain up calls
@@ -1209,8 +1519,15 @@ var Chartist = {
1209
1519
  * @memberof Chartist.Base
1210
1520
  */
1211
1521
  function detach() {
1212
- window.removeEventListener('resize', this.resizeListener);
1213
- this.optionsProvider.removeMediaQueryListeners();
1522
+ // Only detach if initialization already occurred on this chart. If this chart still hasn't initialized (therefore
1523
+ // the initializationTimeoutId is still a valid timeout reference, we will clear the timeout
1524
+ if(!this.initializeTimeoutId) {
1525
+ window.removeEventListener('resize', this.resizeListener);
1526
+ this.optionsProvider.removeMediaQueryListeners();
1527
+ } else {
1528
+ window.clearTimeout(this.initializeTimeoutId);
1529
+ }
1530
+
1214
1531
  return this;
1215
1532
  }
1216
1533
 
@@ -1269,7 +1586,7 @@ var Chartist = {
1269
1586
  });
1270
1587
 
1271
1588
  // Create the first chart
1272
- this.createChart(this.optionsProvider.currentOptions);
1589
+ this.createChart(this.optionsProvider.getCurrentOptions());
1273
1590
 
1274
1591
  // As chart is initialized from the event loop now we can reset our timeout reference
1275
1592
  // This is important if the chart gets initialized on the same element twice
@@ -1302,14 +1619,7 @@ var Chartist = {
1302
1619
  if(this.container) {
1303
1620
  // If chartist was already initialized in this container we are detaching all event listeners first
1304
1621
  if(this.container.__chartist__) {
1305
- if(this.container.__chartist__.initializeTimeoutId) {
1306
- // If the initializeTimeoutId is still set we can safely assume that the initialization function has not
1307
- // been called yet from the event loop. Therefore we should cancel the timeout and don't need to detach
1308
- window.clearTimeout(this.container.__chartist__.initializeTimeoutId);
1309
- } else {
1310
- // The timeout reference has already been reset which means we need to detach the old chart first
1311
- this.container.__chartist__.detach();
1312
- }
1622
+ this.container.__chartist__.detach();
1313
1623
  }
1314
1624
 
1315
1625
  this.container.__chartist__ = this;
@@ -1363,7 +1673,7 @@ var Chartist = {
1363
1673
  *
1364
1674
  * @memberof Chartist.Svg
1365
1675
  * @constructor
1366
- * @param {String|SVGElement} name The name of the SVG element to create or an SVG dom element which should be wrapped into Chartist.Svg
1676
+ * @param {String|Element} name The name of the SVG element to create or an SVG dom element which should be wrapped into Chartist.Svg
1367
1677
  * @param {Object} attributes An object with properties that will be added as attributes to the SVG element that is created. Attributes with undefined values will not be added.
1368
1678
  * @param {String} className This class or class list will be added to the SVG element
1369
1679
  * @param {Object} parent The parent SVG wrapper object where this newly created wrapper and it's element will be attached to as child
@@ -1371,7 +1681,7 @@ var Chartist = {
1371
1681
  */
1372
1682
  function Svg(name, attributes, className, parent, insertFirst) {
1373
1683
  // If Svg is getting called with an SVG element we just return the wrapper
1374
- if(name instanceof SVGElement) {
1684
+ if(name instanceof Element) {
1375
1685
  this._node = name;
1376
1686
  } else {
1377
1687
  this._node = document.createElementNS(svgNs, name);
@@ -1380,21 +1690,21 @@ var Chartist = {
1380
1690
  if(name === 'svg') {
1381
1691
  this._node.setAttributeNS(xmlNs, Chartist.xmlNs.qualifiedName, Chartist.xmlNs.uri);
1382
1692
  }
1693
+ }
1383
1694
 
1384
- if(attributes) {
1385
- this.attr(attributes);
1386
- }
1695
+ if(attributes) {
1696
+ this.attr(attributes);
1697
+ }
1387
1698
 
1388
- if(className) {
1389
- this.addClass(className);
1390
- }
1699
+ if(className) {
1700
+ this.addClass(className);
1701
+ }
1391
1702
 
1392
- if(parent) {
1393
- if (insertFirst && parent._node.firstChild) {
1394
- parent._node.insertBefore(this._node, parent._node.firstChild);
1395
- } else {
1396
- parent._node.appendChild(this._node);
1397
- }
1703
+ if(parent) {
1704
+ if (insertFirst && parent._node.firstChild) {
1705
+ parent._node.insertBefore(this._node, parent._node.firstChild);
1706
+ } else {
1707
+ parent._node.appendChild(this._node);
1398
1708
  }
1399
1709
  }
1400
1710
  }
@@ -1449,6 +1759,7 @@ var Chartist = {
1449
1759
  /**
1450
1760
  * Returns the parent Chartist.SVG wrapper object
1451
1761
  *
1762
+ * @memberof Chartist.Svg
1452
1763
  * @return {Chartist.Svg} Returns a Chartist.Svg wrapper around the parent node of the current node. If the parent node is not existing or it's not an SVG node then this function will return null.
1453
1764
  */
1454
1765
  function parent() {
@@ -1458,6 +1769,7 @@ var Chartist = {
1458
1769
  /**
1459
1770
  * This method returns a Chartist.Svg wrapper around the root SVG element of the current tree.
1460
1771
  *
1772
+ * @memberof Chartist.Svg
1461
1773
  * @return {Chartist.Svg} The root SVG element wrapped in a Chartist.Svg element
1462
1774
  */
1463
1775
  function root() {
@@ -1471,6 +1783,7 @@ var Chartist = {
1471
1783
  /**
1472
1784
  * Find the first child SVG element of the current element that matches a CSS selector. The returned object is a Chartist.Svg wrapper.
1473
1785
  *
1786
+ * @memberof Chartist.Svg
1474
1787
  * @param {String} selector A CSS selector that is used to query for child SVG elements
1475
1788
  * @return {Chartist.Svg} The SVG wrapper for the element found or null if no element was found
1476
1789
  */
@@ -1482,6 +1795,7 @@ var Chartist = {
1482
1795
  /**
1483
1796
  * Find the all child SVG elements of the current element that match a CSS selector. The returned object is a Chartist.Svg.List wrapper.
1484
1797
  *
1798
+ * @memberof Chartist.Svg
1485
1799
  * @param {String} selector A CSS selector that is used to query for child SVG elements
1486
1800
  * @return {Chartist.Svg.List} The SVG wrapper list for the element found or null if no element was found
1487
1801
  */
@@ -1647,6 +1961,24 @@ var Chartist = {
1647
1961
  return this;
1648
1962
  }
1649
1963
 
1964
+ /**
1965
+ * "Save" way to get property value from svg BoundingBox.
1966
+ * This is a workaround. Firefox throws an NS_ERROR_FAILURE error if getBBox() is called on an invisible node.
1967
+ * See [NS_ERROR_FAILURE: Component returned failure code: 0x80004005](http://jsfiddle.net/sym3tri/kWWDK/)
1968
+ *
1969
+ * @memberof Chartist.Svg
1970
+ * @param {SVGElement} node The svg node to
1971
+ * @param {String} prop The property to fetch (ex.: height, width, ...)
1972
+ * @returns {Number} The value of the given bbox property
1973
+ */
1974
+ function getBBoxProperty(node, prop) {
1975
+ try {
1976
+ return node.getBBox()[prop];
1977
+ } catch(e) {}
1978
+
1979
+ return 0;
1980
+ }
1981
+
1650
1982
  /**
1651
1983
  * Get element height with fallback to svg BoundingBox or parent container dimensions:
1652
1984
  * See [bugzilla.mozilla.org](https://bugzilla.mozilla.org/show_bug.cgi?id=530985)
@@ -1655,7 +1987,7 @@ var Chartist = {
1655
1987
  * @return {Number} The elements height in pixels
1656
1988
  */
1657
1989
  function height() {
1658
- return this._node.clientHeight || Math.round(this._node.getBBox().height) || this._node.parentNode.clientHeight;
1990
+ return this._node.clientHeight || Math.round(getBBoxProperty(this._node, 'height')) || this._node.parentNode.clientHeight;
1659
1991
  }
1660
1992
 
1661
1993
  /**
@@ -1666,7 +1998,7 @@ var Chartist = {
1666
1998
  * @return {Number} The elements width in pixels
1667
1999
  */
1668
2000
  function width() {
1669
- return this._node.clientWidth || Math.round(this._node.getBBox().width) || this._node.parentNode.clientWidth;
2001
+ return this._node.clientWidth || Math.round(getBBoxProperty(this._node, 'width')) || this._node.parentNode.clientWidth;
1670
2002
  }
1671
2003
 
1672
2004
  /**
@@ -1852,7 +2184,7 @@ var Chartist = {
1852
2184
  * @return {Boolean} True of false if the feature is supported or not
1853
2185
  */
1854
2186
  Chartist.Svg.isSupported = function(feature) {
1855
- return document.implementation.hasFeature('www.http://w3.org/TR/SVG11/feature#' + feature, '1.1');
2187
+ return document.implementation.hasFeature('http://www.w3.org/TR/SVG11/feature#' + feature, '1.1');
1856
2188
  };
1857
2189
 
1858
2190
  /**
@@ -1964,10 +2296,12 @@ var Chartist = {
1964
2296
  accuracy: 3
1965
2297
  };
1966
2298
 
1967
- function element(command, params, pathElements, pos, relative) {
1968
- pathElements.splice(pos, 0, Chartist.extend({
2299
+ function element(command, params, pathElements, pos, relative, data) {
2300
+ var pathElement = Chartist.extend({
1969
2301
  command: relative ? command.toLowerCase() : command.toUpperCase()
1970
- }, params));
2302
+ }, params, data ? { data: data } : {} );
2303
+
2304
+ pathElements.splice(pos, 0, pathElement);
1971
2305
  }
1972
2306
 
1973
2307
  function forEachParam(pathElements, cb) {
@@ -2028,13 +2362,14 @@ var Chartist = {
2028
2362
  * @param {Number} x The x coordinate for the move element.
2029
2363
  * @param {Number} y The y coordinate for the move element.
2030
2364
  * @param {Boolean} [relative] If set to true the move element will be created with relative coordinates (lowercase letter)
2365
+ * @param {*} [data] Any data that should be stored with the element object that will be accessible in pathElement
2031
2366
  * @return {Chartist.Svg.Path} The current path object for easy call chaining.
2032
2367
  */
2033
- function move(x, y, relative) {
2368
+ function move(x, y, relative, data) {
2034
2369
  element('M', {
2035
2370
  x: +x,
2036
2371
  y: +y
2037
- }, this.pathElements, this.pos++, relative);
2372
+ }, this.pathElements, this.pos++, relative, data);
2038
2373
  return this;
2039
2374
  }
2040
2375
 
@@ -2045,13 +2380,14 @@ var Chartist = {
2045
2380
  * @param {Number} x The x coordinate for the line element.
2046
2381
  * @param {Number} y The y coordinate for the line element.
2047
2382
  * @param {Boolean} [relative] If set to true the line element will be created with relative coordinates (lowercase letter)
2383
+ * @param {*} [data] Any data that should be stored with the element object that will be accessible in pathElement
2048
2384
  * @return {Chartist.Svg.Path} The current path object for easy call chaining.
2049
2385
  */
2050
- function line(x, y, relative) {
2386
+ function line(x, y, relative, data) {
2051
2387
  element('L', {
2052
2388
  x: +x,
2053
2389
  y: +y
2054
- }, this.pathElements, this.pos++, relative);
2390
+ }, this.pathElements, this.pos++, relative, data);
2055
2391
  return this;
2056
2392
  }
2057
2393
 
@@ -2066,9 +2402,10 @@ var Chartist = {
2066
2402
  * @param {Number} x The x coordinate for the target point of the curve element.
2067
2403
  * @param {Number} y The y coordinate for the target point of the curve element.
2068
2404
  * @param {Boolean} [relative] If set to true the curve element will be created with relative coordinates (lowercase letter)
2405
+ * @param {*} [data] Any data that should be stored with the element object that will be accessible in pathElement
2069
2406
  * @return {Chartist.Svg.Path} The current path object for easy call chaining.
2070
2407
  */
2071
- function curve(x1, y1, x2, y2, x, y, relative) {
2408
+ function curve(x1, y1, x2, y2, x, y, relative, data) {
2072
2409
  element('C', {
2073
2410
  x1: +x1,
2074
2411
  y1: +y1,
@@ -2076,7 +2413,7 @@ var Chartist = {
2076
2413
  y2: +y2,
2077
2414
  x: +x,
2078
2415
  y: +y
2079
- }, this.pathElements, this.pos++, relative);
2416
+ }, this.pathElements, this.pos++, relative, data);
2080
2417
  return this;
2081
2418
  }
2082
2419
 
@@ -2092,9 +2429,10 @@ var Chartist = {
2092
2429
  * @param {Number} x The x coordinate for the target point of the curve element.
2093
2430
  * @param {Number} y The y coordinate for the target point of the curve element.
2094
2431
  * @param {Boolean} [relative] If set to true the curve element will be created with relative coordinates (lowercase letter)
2432
+ * @param {*} [data] Any data that should be stored with the element object that will be accessible in pathElement
2095
2433
  * @return {Chartist.Svg.Path} The current path object for easy call chaining.
2096
2434
  */
2097
- function arc(rx, ry, xAr, lAf, sf, x, y, relative) {
2435
+ function arc(rx, ry, xAr, lAf, sf, x, y, relative, data) {
2098
2436
  element('A', {
2099
2437
  rx: +rx,
2100
2438
  ry: +ry,
@@ -2103,7 +2441,7 @@ var Chartist = {
2103
2441
  sf: +sf,
2104
2442
  x: +x,
2105
2443
  y: +y
2106
- }, this.pathElements, this.pos++, relative);
2444
+ }, this.pathElements, this.pos++, relative, data);
2107
2445
  return this;
2108
2446
  }
2109
2447
 
@@ -2233,10 +2571,11 @@ var Chartist = {
2233
2571
  * This function clones a whole path object with all its properties. This is a deep clone and path element objects will also be cloned.
2234
2572
  *
2235
2573
  * @memberof Chartist.Svg.Path
2574
+ * @param {Boolean} [close] Optional option to set the new cloned path to closed. If not specified or false, the original path close option will be used.
2236
2575
  * @return {Chartist.Svg.Path}
2237
2576
  */
2238
- function clone() {
2239
- var c = new Chartist.Svg.Path(this.close);
2577
+ function clone(close) {
2578
+ var c = new Chartist.Svg.Path(close || this.close);
2240
2579
  c.pos = this.pos;
2241
2580
  c.pathElements = this.pathElements.slice().map(function cloneElements(pathElement) {
2242
2581
  return Chartist.extend({}, pathElement);
@@ -2245,6 +2584,50 @@ var Chartist = {
2245
2584
  return c;
2246
2585
  }
2247
2586
 
2587
+ /**
2588
+ * Split a Svg.Path object by a specific command in the path chain. The path chain will be split and an array of newly created paths objects will be returned. This is useful if you'd like to split an SVG path by it's move commands, for example, in order to isolate chunks of drawings.
2589
+ *
2590
+ * @memberof Chartist.Svg.Path
2591
+ * @param {String} command The command you'd like to use to split the path
2592
+ * @return {Array<Chartist.Svg.Path>}
2593
+ */
2594
+ function splitByCommand(command) {
2595
+ var split = [
2596
+ new Chartist.Svg.Path()
2597
+ ];
2598
+
2599
+ this.pathElements.forEach(function(pathElement) {
2600
+ if(pathElement.command === command.toUpperCase() && split[split.length - 1].pathElements.length !== 0) {
2601
+ split.push(new Chartist.Svg.Path());
2602
+ }
2603
+
2604
+ split[split.length - 1].pathElements.push(pathElement);
2605
+ });
2606
+
2607
+ return split;
2608
+ }
2609
+
2610
+ /**
2611
+ * This static function on `Chartist.Svg.Path` is joining multiple paths together into one paths.
2612
+ *
2613
+ * @memberof Chartist.Svg.Path
2614
+ * @param {Array<Chartist.Svg.Path>} paths A list of paths to be joined together. The order is important.
2615
+ * @param {boolean} close If the newly created path should be a closed path
2616
+ * @param {Object} options Path options for the newly created path.
2617
+ * @return {Chartist.Svg.Path}
2618
+ */
2619
+
2620
+ function join(paths, close, options) {
2621
+ var joinedPath = new Chartist.Svg.Path(close, options);
2622
+ for(var i = 0; i < paths.length; i++) {
2623
+ var path = paths[i];
2624
+ for(var j = 0; j < path.pathElements.length; j++) {
2625
+ joinedPath.pathElements.push(path.pathElements[j]);
2626
+ }
2627
+ }
2628
+ return joinedPath;
2629
+ }
2630
+
2248
2631
  Chartist.Svg.Path = Chartist.Class.extend({
2249
2632
  constructor: SvgPath,
2250
2633
  position: position,
@@ -2258,17 +2641,14 @@ var Chartist = {
2258
2641
  transform: transform,
2259
2642
  parse: parse,
2260
2643
  stringify: stringify,
2261
- clone: clone
2644
+ clone: clone,
2645
+ splitByCommand: splitByCommand
2262
2646
  });
2263
2647
 
2264
2648
  Chartist.Svg.Path.elementDescriptions = elementDescriptions;
2649
+ Chartist.Svg.Path.join = join;
2265
2650
  }(window, document, Chartist));
2266
- ;/**
2267
- * Axis base class used to implement different axis types
2268
- *
2269
- * @module Chartist.Axis
2270
- */
2271
- /* global Chartist */
2651
+ ;/* global Chartist */
2272
2652
  (function (window, document, Chartist) {
2273
2653
  'use strict';
2274
2654
 
@@ -2291,19 +2671,91 @@ var Chartist = {
2291
2671
  }
2292
2672
  };
2293
2673
 
2294
- function Axis(units, chartRect, transform, labelOffset, options) {
2674
+ function Axis(units, chartRect, ticks, options) {
2295
2675
  this.units = units;
2296
2676
  this.counterUnits = units === axisUnits.x ? axisUnits.y : axisUnits.x;
2297
2677
  this.chartRect = chartRect;
2298
2678
  this.axisLength = chartRect[units.rectEnd] - chartRect[units.rectStart];
2299
2679
  this.gridOffset = chartRect[units.rectOffset];
2300
- this.transform = transform;
2301
- this.labelOffset = labelOffset;
2680
+ this.ticks = ticks;
2302
2681
  this.options = options;
2303
2682
  }
2304
2683
 
2684
+ function createGridAndLabels(gridGroup, labelGroup, useForeignObject, chartOptions, eventEmitter) {
2685
+ var axisOptions = chartOptions['axis' + this.units.pos.toUpperCase()];
2686
+ var projectedValues = this.ticks.map(this.projectValue.bind(this));
2687
+ var labelValues = this.ticks.map(axisOptions.labelInterpolationFnc);
2688
+
2689
+ projectedValues.forEach(function(projectedValue, index) {
2690
+ var labelOffset = {
2691
+ x: 0,
2692
+ y: 0
2693
+ };
2694
+
2695
+ // TODO: Find better solution for solving this problem
2696
+ // Calculate how much space we have available for the label
2697
+ var labelLength;
2698
+ if(projectedValues[index + 1]) {
2699
+ // If we still have one label ahead, we can calculate the distance to the next tick / label
2700
+ labelLength = projectedValues[index + 1] - projectedValue;
2701
+ } else {
2702
+ // If we don't have a label ahead and we have only two labels in total, we just take the remaining distance to
2703
+ // on the whole axis length. We limit that to a minimum of 30 pixel, so that labels close to the border will
2704
+ // still be visible inside of the chart padding.
2705
+ labelLength = Math.max(this.axisLength - projectedValue, 30);
2706
+ }
2707
+
2708
+ // Skip grid lines and labels where interpolated label values are falsey (execpt for 0)
2709
+ if(!labelValues[index] && labelValues[index] !== 0) {
2710
+ return;
2711
+ }
2712
+
2713
+ // Transform to global coordinates using the chartRect
2714
+ // We also need to set the label offset for the createLabel function
2715
+ if(this.units.pos === 'x') {
2716
+ projectedValue = this.chartRect.x1 + projectedValue;
2717
+ labelOffset.x = chartOptions.axisX.labelOffset.x;
2718
+
2719
+ // If the labels should be positioned in start position (top side for vertical axis) we need to set a
2720
+ // different offset as for positioned with end (bottom)
2721
+ if(chartOptions.axisX.position === 'start') {
2722
+ labelOffset.y = this.chartRect.padding.top + chartOptions.axisX.labelOffset.y + (useForeignObject ? 5 : 20);
2723
+ } else {
2724
+ labelOffset.y = this.chartRect.y1 + chartOptions.axisX.labelOffset.y + (useForeignObject ? 5 : 20);
2725
+ }
2726
+ } else {
2727
+ projectedValue = this.chartRect.y1 - projectedValue;
2728
+ labelOffset.y = chartOptions.axisY.labelOffset.y - (useForeignObject ? labelLength : 0);
2729
+
2730
+ // If the labels should be positioned in start position (left side for horizontal axis) we need to set a
2731
+ // different offset as for positioned with end (right side)
2732
+ if(chartOptions.axisY.position === 'start') {
2733
+ labelOffset.x = useForeignObject ? this.chartRect.padding.left + chartOptions.axisY.labelOffset.x : this.chartRect.x1 - 10;
2734
+ } else {
2735
+ labelOffset.x = this.chartRect.x2 + chartOptions.axisY.labelOffset.x + 10;
2736
+ }
2737
+ }
2738
+
2739
+ if(axisOptions.showGrid) {
2740
+ Chartist.createGrid(projectedValue, index, this, this.gridOffset, this.chartRect[this.counterUnits.len](), gridGroup, [
2741
+ chartOptions.classNames.grid,
2742
+ chartOptions.classNames[this.units.dir]
2743
+ ], eventEmitter);
2744
+ }
2745
+
2746
+ if(axisOptions.showLabel) {
2747
+ Chartist.createLabel(projectedValue, labelLength, index, labelValues, this, axisOptions.offset, labelOffset, labelGroup, [
2748
+ chartOptions.classNames.label,
2749
+ chartOptions.classNames[this.units.dir],
2750
+ chartOptions.classNames[axisOptions.position]
2751
+ ], useForeignObject, eventEmitter);
2752
+ }
2753
+ }.bind(this));
2754
+ }
2755
+
2305
2756
  Chartist.Axis = Chartist.Class.extend({
2306
2757
  constructor: Axis,
2758
+ createGridAndLabels: createGridAndLabels,
2307
2759
  projectValue: function(value, index, data) {
2308
2760
  throw new Error('Base axis can\'t be instantiated!');
2309
2761
  }
@@ -2313,40 +2765,121 @@ var Chartist = {
2313
2765
 
2314
2766
  }(window, document, Chartist));
2315
2767
  ;/**
2316
- * The linear scale axis uses standard linear scale projection of values along an axis.
2768
+ * The auto scale axis uses standard linear scale projection of values along an axis. It uses order of magnitude to find a scale automatically and evaluates the available space in order to find the perfect amount of ticks for your chart.
2769
+ * **Options**
2770
+ * The following options are used by this axis in addition to the default axis options outlined in the axis configuration of the chart default settings.
2771
+ * ```javascript
2772
+ * var options = {
2773
+ * // If high is specified then the axis will display values explicitly up to this value and the computed maximum from the data is ignored
2774
+ * high: 100,
2775
+ * // If low is specified then the axis will display values explicitly down to this value and the computed minimum from the data is ignored
2776
+ * low: 0,
2777
+ * // This option will be used when finding the right scale division settings. The amount of ticks on the scale will be determined so that as many ticks as possible will be displayed, while not violating this minimum required space (in pixel).
2778
+ * scaleMinSpace: 20,
2779
+ * // Can be set to true or false. If set to true, the scale will be generated with whole numbers only.
2780
+ * onlyInteger: true,
2781
+ * // The reference value can be used to make sure that this value will always be on the chart. This is especially useful on bipolar charts where the bipolar center always needs to be part of the chart.
2782
+ * referenceValue: 5
2783
+ * };
2784
+ * ```
2317
2785
  *
2318
- * @module Chartist.LinearScaleAxis
2786
+ * @module Chartist.AutoScaleAxis
2319
2787
  */
2320
2788
  /* global Chartist */
2321
2789
  (function (window, document, Chartist) {
2322
2790
  'use strict';
2323
2791
 
2324
- function LinearScaleAxis(axisUnit, chartRect, transform, labelOffset, options) {
2325
- Chartist.LinearScaleAxis.super.constructor.call(this,
2792
+ function AutoScaleAxis(axisUnit, data, chartRect, options) {
2793
+ // Usually we calculate highLow based on the data but this can be overriden by a highLow object in the options
2794
+ var highLow = options.highLow || Chartist.getHighLow(data.normalized, options, axisUnit.pos);
2795
+ this.bounds = Chartist.getBounds(chartRect[axisUnit.rectEnd] - chartRect[axisUnit.rectStart], highLow, options.scaleMinSpace || 20, options.onlyInteger);
2796
+ this.range = {
2797
+ min: this.bounds.min,
2798
+ max: this.bounds.max
2799
+ };
2800
+
2801
+ Chartist.AutoScaleAxis.super.constructor.call(this,
2326
2802
  axisUnit,
2327
2803
  chartRect,
2328
- transform,
2329
- labelOffset,
2804
+ this.bounds.values,
2330
2805
  options);
2331
-
2332
- this.bounds = Chartist.getBounds(this.axisLength, options.highLow, options.scaleMinSpace, options.referenceValue);
2333
2806
  }
2334
2807
 
2335
2808
  function projectValue(value) {
2336
- return {
2337
- pos: this.axisLength * (value - this.bounds.min) / (this.bounds.range + this.bounds.step),
2338
- len: Chartist.projectLength(this.axisLength, this.bounds.step, this.bounds)
2809
+ return this.axisLength * (+Chartist.getMultiValue(value, this.units.pos) - this.bounds.min) / this.bounds.range;
2810
+ }
2811
+
2812
+ Chartist.AutoScaleAxis = Chartist.Axis.extend({
2813
+ constructor: AutoScaleAxis,
2814
+ projectValue: projectValue
2815
+ });
2816
+
2817
+ }(window, document, Chartist));
2818
+ ;/**
2819
+ * The fixed scale axis uses standard linear projection of values along an axis. It makes use of a divisor option to divide the range provided from the minimum and maximum value or the options high and low that will override the computed minimum and maximum.
2820
+ * **Options**
2821
+ * The following options are used by this axis in addition to the default axis options outlined in the axis configuration of the chart default settings.
2822
+ * ```javascript
2823
+ * var options = {
2824
+ * // If high is specified then the axis will display values explicitly up to this value and the computed maximum from the data is ignored
2825
+ * high: 100,
2826
+ * // If low is specified then the axis will display values explicitly down to this value and the computed minimum from the data is ignored
2827
+ * low: 0,
2828
+ * // If specified then the value range determined from minimum to maximum (or low and high) will be divided by this number and ticks will be generated at those division points. The default divisor is 1.
2829
+ * divisor: 4,
2830
+ * // If ticks is explicitly set, then the axis will not compute the ticks with the divisor, but directly use the data in ticks to determine at what points on the axis a tick need to be generated.
2831
+ * ticks: [1, 10, 20, 30]
2832
+ * };
2833
+ * ```
2834
+ *
2835
+ * @module Chartist.FixedScaleAxis
2836
+ */
2837
+ /* global Chartist */
2838
+ (function (window, document, Chartist) {
2839
+ 'use strict';
2840
+
2841
+ function FixedScaleAxis(axisUnit, data, chartRect, options) {
2842
+ var highLow = options.highLow || Chartist.getHighLow(data.normalized, options, axisUnit.pos);
2843
+ this.divisor = options.divisor || 1;
2844
+ this.ticks = options.ticks || Chartist.times(this.divisor).map(function(value, index) {
2845
+ return highLow.low + (highLow.high - highLow.low) / this.divisor * index;
2846
+ }.bind(this));
2847
+ this.range = {
2848
+ min: highLow.low,
2849
+ max: highLow.high
2339
2850
  };
2851
+
2852
+ Chartist.FixedScaleAxis.super.constructor.call(this,
2853
+ axisUnit,
2854
+ chartRect,
2855
+ this.ticks,
2856
+ options);
2857
+
2858
+ this.stepLength = this.axisLength / this.divisor;
2340
2859
  }
2341
2860
 
2342
- Chartist.LinearScaleAxis = Chartist.Axis.extend({
2343
- constructor: LinearScaleAxis,
2861
+ function projectValue(value) {
2862
+ return this.axisLength * (+Chartist.getMultiValue(value, this.units.pos) - this.range.min) / (this.range.max - this.range.min);
2863
+ }
2864
+
2865
+ Chartist.FixedScaleAxis = Chartist.Axis.extend({
2866
+ constructor: FixedScaleAxis,
2344
2867
  projectValue: projectValue
2345
2868
  });
2346
2869
 
2347
2870
  }(window, document, Chartist));
2348
2871
  ;/**
2349
- * Step axis for step based charts like bar chart or step based line chart
2872
+ * The step axis for step based charts like bar chart or step based line charts. It uses a fixed amount of ticks that will be equally distributed across the whole axis length. The projection is done using the index of the data value rather than the value itself and therefore it's only useful for distribution purpose.
2873
+ * **Options**
2874
+ * The following options are used by this axis in addition to the default axis options outlined in the axis configuration of the chart default settings.
2875
+ * ```javascript
2876
+ * var options = {
2877
+ * // Ticks to be used to distribute across the axis length. As this axis type relies on the index of the value rather than the value, arbitrary data that can be converted to a string can be used as ticks.
2878
+ * ticks: ['One', 'Two', 'Three'],
2879
+ * // If set to true the full width will be used to distribute the values where the last value will be at the maximum of the axis length. If false the spaces between the ticks will be evenly distributed instead.
2880
+ * stretch: true
2881
+ * };
2882
+ * ```
2350
2883
  *
2351
2884
  * @module Chartist.StepAxis
2352
2885
  */
@@ -2354,22 +2887,18 @@ var Chartist = {
2354
2887
  (function (window, document, Chartist) {
2355
2888
  'use strict';
2356
2889
 
2357
- function StepAxis(axisUnit, chartRect, transform, labelOffset, options) {
2890
+ function StepAxis(axisUnit, data, chartRect, options) {
2358
2891
  Chartist.StepAxis.super.constructor.call(this,
2359
2892
  axisUnit,
2360
2893
  chartRect,
2361
- transform,
2362
- labelOffset,
2894
+ options.ticks,
2363
2895
  options);
2364
2896
 
2365
- this.stepLength = this.axisLength / (options.stepCount - (options.stretch ? 1 : 0));
2897
+ this.stepLength = this.axisLength / (options.ticks.length - (options.stretch ? 1 : 0));
2366
2898
  }
2367
2899
 
2368
2900
  function projectValue(value, index) {
2369
- return {
2370
- pos: this.stepLength * index,
2371
- len: this.stepLength
2372
- };
2901
+ return this.stepLength * index;
2373
2902
  }
2374
2903
 
2375
2904
  Chartist.StepAxis = Chartist.Axis.extend({
@@ -2399,6 +2928,8 @@ var Chartist = {
2399
2928
  axisX: {
2400
2929
  // The offset of the labels to the chart area
2401
2930
  offset: 30,
2931
+ // Position where labels are placed. Can be set to `start` or `end` where `start` is equivalent to left or top on vertical axis and `end` is equivalent to right or bottom on horizontal axis.
2932
+ position: 'end',
2402
2933
  // Allows you to correct label positioning on this axis by positive or negative x and y offset.
2403
2934
  labelOffset: {
2404
2935
  x: 0,
@@ -2409,12 +2940,16 @@ var Chartist = {
2409
2940
  // If the axis grid should be drawn or not
2410
2941
  showGrid: true,
2411
2942
  // Interpolation function that allows you to intercept the value from the axis label
2412
- labelInterpolationFnc: Chartist.noop
2943
+ labelInterpolationFnc: Chartist.noop,
2944
+ // Set the axis type to be used to project values on this axis. If not defined, Chartist.StepAxis will be used for the X-Axis, where the ticks option will be set to the labels in the data and the stretch option will be set to the global fullWidth option. This type can be changed to any axis constructor available (e.g. Chartist.FixedScaleAxis), where all axis options should be present here.
2945
+ type: undefined
2413
2946
  },
2414
2947
  // Options for Y-Axis
2415
2948
  axisY: {
2416
2949
  // The offset of the labels to the chart area
2417
2950
  offset: 40,
2951
+ // Position where labels are placed. Can be set to `start` or `end` where `start` is equivalent to left or top on vertical axis and `end` is equivalent to right or bottom on horizontal axis.
2952
+ position: 'start',
2418
2953
  // Allows you to correct label positioning on this axis by positive or negative x and y offset.
2419
2954
  labelOffset: {
2420
2955
  x: 0,
@@ -2426,8 +2961,12 @@ var Chartist = {
2426
2961
  showGrid: true,
2427
2962
  // Interpolation function that allows you to intercept the value from the axis label
2428
2963
  labelInterpolationFnc: Chartist.noop,
2964
+ // Set the axis type to be used to project values on this axis. If not defined, Chartist.AutoScaleAxis will be used for the Y-Axis, where the high and low options will be set to the global high and low options. This type can be changed to any axis constructor available (e.g. Chartist.FixedScaleAxis), where all axis options should be present here.
2965
+ type: undefined,
2429
2966
  // This value specifies the minimum height in pixel of the scale steps
2430
- scaleMinSpace: 20
2967
+ scaleMinSpace: 20,
2968
+ // Use only integer values (whole numbers) for the scale steps
2969
+ onlyInteger: false
2431
2970
  },
2432
2971
  // Specify a fixed width for the chart as a string (i.e. '100px' or '50%')
2433
2972
  width: undefined,
@@ -2448,7 +2987,12 @@ var Chartist = {
2448
2987
  // Overriding the natural high of the chart allows you to zoom in or limit the charts highest displayed value
2449
2988
  high: undefined,
2450
2989
  // Padding of the chart drawing area to the container element and labels as a number or padding object {top: 5, right: 5, bottom: 5, left: 5}
2451
- chartPadding: 5,
2990
+ chartPadding: {
2991
+ top: 15,
2992
+ right: 15,
2993
+ bottom: 5,
2994
+ left: 10
2995
+ },
2452
2996
  // When set to true, the last grid line on the x-axis is not drawn and the chart elements will expand to the full available width of the chart. For the last label to be drawn correctly you might need to add chart padding or offset the last label with a draw event handler.
2453
2997
  fullWidth: false,
2454
2998
  // If true the whole data is reversed including labels, the series order as well as the whole series data arrays.
@@ -2465,7 +3009,9 @@ var Chartist = {
2465
3009
  grid: 'ct-grid',
2466
3010
  gridGroup: 'ct-grids',
2467
3011
  vertical: 'ct-vertical',
2468
- horizontal: 'ct-horizontal'
3012
+ horizontal: 'ct-horizontal',
3013
+ start: 'ct-start',
3014
+ end: 'ct-end'
2469
3015
  }
2470
3016
  };
2471
3017
 
@@ -2474,189 +3020,197 @@ var Chartist = {
2474
3020
  *
2475
3021
  */
2476
3022
  function createChart(options) {
2477
- var seriesGroups = [],
2478
- normalizedData = Chartist.normalizeDataArray(Chartist.getDataArray(this.data, options.reverseData), this.data.labels.length),
2479
- normalizedPadding = Chartist.normalizePadding(options.chartPadding, defaultOptions.padding);
3023
+ var data = {
3024
+ raw: this.data,
3025
+ normalized: Chartist.getDataArray(this.data, options.reverseData, true)
3026
+ };
2480
3027
 
2481
3028
  // Create new svg object
2482
3029
  this.svg = Chartist.createSvg(this.container, options.width, options.height, options.classNames.chart);
3030
+ // Create groups for labels, grid and series
3031
+ var gridGroup = this.svg.elem('g').addClass(options.classNames.gridGroup);
3032
+ var seriesGroup = this.svg.elem('g');
3033
+ var labelGroup = this.svg.elem('g').addClass(options.classNames.labelGroup);
2483
3034
 
2484
3035
  var chartRect = Chartist.createChartRect(this.svg, options, defaultOptions.padding);
3036
+ var axisX, axisY;
2485
3037
 
2486
- var highLow = Chartist.getHighLow(normalizedData);
2487
- // Overrides of high / low from settings
2488
- highLow.high = +options.high || (options.high === 0 ? 0 : highLow.high);
2489
- highLow.low = +options.low || (options.low === 0 ? 0 : highLow.low);
2490
-
2491
- var axisX = new Chartist.StepAxis(
2492
- Chartist.Axis.units.x,
2493
- chartRect,
2494
- function xAxisTransform(projectedValue) {
2495
- projectedValue.pos = chartRect.x1 + projectedValue.pos;
2496
- return projectedValue;
2497
- },
2498
- {
2499
- x: options.axisX.labelOffset.x,
2500
- y: chartRect.y1 + options.axisX.labelOffset.y + (this.supportsForeignObject ? 5 : 20)
2501
- },
2502
- {
2503
- stepCount: this.data.labels.length,
3038
+ if(options.axisX.type === undefined) {
3039
+ axisX = new Chartist.StepAxis(Chartist.Axis.units.x, data, chartRect, Chartist.extend({}, options.axisX, {
3040
+ ticks: data.raw.labels,
2504
3041
  stretch: options.fullWidth
2505
- }
2506
- );
2507
-
2508
- var axisY = new Chartist.LinearScaleAxis(
2509
- Chartist.Axis.units.y,
2510
- chartRect,
2511
- function yAxisTransform(projectedValue) {
2512
- projectedValue.pos = chartRect.y1 - projectedValue.pos;
2513
- return projectedValue;
2514
- },
2515
- {
2516
- x: normalizedPadding.left + options.axisY.labelOffset.x + (this.supportsForeignObject ? -10 : 0),
2517
- y: options.axisY.labelOffset.y + (this.supportsForeignObject ? -15 : 0)
2518
- },
2519
- {
2520
- highLow: highLow,
2521
- scaleMinSpace: options.axisY.scaleMinSpace
2522
- }
2523
- );
2524
-
2525
- // Start drawing
2526
- var labelGroup = this.svg.elem('g').addClass(options.classNames.labelGroup),
2527
- gridGroup = this.svg.elem('g').addClass(options.classNames.gridGroup);
3042
+ }));
3043
+ } else {
3044
+ axisX = options.axisX.type.call(Chartist, Chartist.Axis.units.x, data, chartRect, options.axisX);
3045
+ }
2528
3046
 
2529
- Chartist.createAxis(
2530
- axisX,
2531
- this.data.labels,
2532
- chartRect,
2533
- gridGroup,
2534
- labelGroup,
2535
- this.supportsForeignObject,
2536
- options,
2537
- this.eventEmitter
2538
- );
3047
+ if(options.axisY.type === undefined) {
3048
+ axisY = new Chartist.AutoScaleAxis(Chartist.Axis.units.y, data, chartRect, Chartist.extend({}, options.axisY, {
3049
+ high: Chartist.isNum(options.high) ? options.high : options.axisY.high,
3050
+ low: Chartist.isNum(options.low) ? options.low : options.axisY.low
3051
+ }));
3052
+ } else {
3053
+ axisY = options.axisY.type.call(Chartist, Chartist.Axis.units.y, data, chartRect, options.axisY);
3054
+ }
2539
3055
 
2540
- Chartist.createAxis(
2541
- axisY,
2542
- axisY.bounds.values,
2543
- chartRect,
2544
- gridGroup,
2545
- labelGroup,
2546
- this.supportsForeignObject,
2547
- options,
2548
- this.eventEmitter
2549
- );
3056
+ axisX.createGridAndLabels(gridGroup, labelGroup, this.supportsForeignObject, options, this.eventEmitter);
3057
+ axisY.createGridAndLabels(gridGroup, labelGroup, this.supportsForeignObject, options, this.eventEmitter);
2550
3058
 
2551
3059
  // Draw the series
2552
- this.data.series.forEach(function(series, seriesIndex) {
2553
- seriesGroups[seriesIndex] = this.svg.elem('g');
3060
+ data.raw.series.forEach(function(series, seriesIndex) {
3061
+ var seriesElement = seriesGroup.elem('g');
2554
3062
 
2555
3063
  // Write attributes to series group element. If series name or meta is undefined the attributes will not be written
2556
- seriesGroups[seriesIndex].attr({
3064
+ seriesElement.attr({
2557
3065
  'series-name': series.name,
2558
3066
  'meta': Chartist.serialize(series.meta)
2559
3067
  }, Chartist.xmlNs.uri);
2560
3068
 
2561
3069
  // Use series class from series data or if not set generate one
2562
- seriesGroups[seriesIndex].addClass([
3070
+ seriesElement.addClass([
2563
3071
  options.classNames.series,
2564
3072
  (series.className || options.classNames.series + '-' + Chartist.alphaNumerate(seriesIndex))
2565
3073
  ].join(' '));
2566
3074
 
2567
- var pathCoordinates = [];
3075
+ var pathCoordinates = [],
3076
+ pathData = [];
2568
3077
 
2569
- normalizedData[seriesIndex].forEach(function(value, valueIndex) {
3078
+ data.normalized[seriesIndex].forEach(function(value, valueIndex) {
2570
3079
  var p = {
2571
- x: chartRect.x1 + axisX.projectValue(value, valueIndex, normalizedData[seriesIndex]).pos,
2572
- y: chartRect.y1 - axisY.projectValue(value, valueIndex, normalizedData[seriesIndex]).pos
3080
+ x: chartRect.x1 + axisX.projectValue(value, valueIndex, data.normalized[seriesIndex]),
3081
+ y: chartRect.y1 - axisY.projectValue(value, valueIndex, data.normalized[seriesIndex])
2573
3082
  };
2574
3083
  pathCoordinates.push(p.x, p.y);
3084
+ pathData.push({
3085
+ value: value,
3086
+ valueIndex: valueIndex,
3087
+ meta: Chartist.getMetaData(series, valueIndex)
3088
+ });
3089
+ }.bind(this));
3090
+
3091
+ var seriesOptions = {
3092
+ lineSmooth: Chartist.getSeriesOption(series, options, 'lineSmooth'),
3093
+ showPoint: Chartist.getSeriesOption(series, options, 'showPoint'),
3094
+ showLine: Chartist.getSeriesOption(series, options, 'showLine'),
3095
+ showArea: Chartist.getSeriesOption(series, options, 'showArea'),
3096
+ areaBase: Chartist.getSeriesOption(series, options, 'areaBase')
3097
+ };
2575
3098
 
2576
- //If we should show points we need to create them now to avoid secondary loop
2577
- // Small offset for Firefox to render squares correctly
2578
- if (options.showPoint) {
2579
- var point = seriesGroups[seriesIndex].elem('line', {
2580
- x1: p.x,
2581
- y1: p.y,
2582
- x2: p.x + 0.01,
2583
- y2: p.y
3099
+ var smoothing = typeof seriesOptions.lineSmooth === 'function' ?
3100
+ seriesOptions.lineSmooth : (seriesOptions.lineSmooth ? Chartist.Interpolation.cardinal() : Chartist.Interpolation.none());
3101
+ // Interpolating path where pathData will be used to annotate each path element so we can trace back the original
3102
+ // index, value and meta data
3103
+ var path = smoothing(pathCoordinates, pathData);
3104
+
3105
+ // If we should show points we need to create them now to avoid secondary loop
3106
+ // Points are drawn from the pathElements returned by the interpolation function
3107
+ // Small offset for Firefox to render squares correctly
3108
+ if (seriesOptions.showPoint) {
3109
+
3110
+ path.pathElements.forEach(function(pathElement) {
3111
+ var point = seriesElement.elem('line', {
3112
+ x1: pathElement.x,
3113
+ y1: pathElement.y,
3114
+ x2: pathElement.x + 0.01,
3115
+ y2: pathElement.y
2584
3116
  }, options.classNames.point).attr({
2585
- 'value': value,
2586
- 'meta': Chartist.getMetaData(series, valueIndex)
3117
+ 'value': [pathElement.data.value.x, pathElement.data.value.y].filter(function(v) {
3118
+ return v;
3119
+ }).join(','),
3120
+ 'meta': pathElement.data.meta
2587
3121
  }, Chartist.xmlNs.uri);
2588
3122
 
2589
3123
  this.eventEmitter.emit('draw', {
2590
3124
  type: 'point',
2591
- value: value,
2592
- index: valueIndex,
2593
- group: seriesGroups[seriesIndex],
3125
+ value: pathElement.data.value,
3126
+ index: pathElement.data.valueIndex,
3127
+ meta: pathElement.data.meta,
3128
+ series: series,
3129
+ seriesIndex: seriesIndex,
3130
+ axisX: axisX,
3131
+ axisY: axisY,
3132
+ group: seriesElement,
2594
3133
  element: point,
2595
- x: p.x,
2596
- y: p.y
3134
+ x: pathElement.x,
3135
+ y: pathElement.y
2597
3136
  });
2598
- }
2599
- }.bind(this));
2600
-
2601
- // TODO: Nicer handling of conditions, maybe composition?
2602
- if (options.showLine || options.showArea) {
2603
- var smoothing = typeof options.lineSmooth === 'function' ?
2604
- options.lineSmooth : (options.lineSmooth ? Chartist.Interpolation.cardinal() : Chartist.Interpolation.none()),
2605
- path = smoothing(pathCoordinates);
2606
-
2607
- if(options.showLine) {
2608
- var line = seriesGroups[seriesIndex].elem('path', {
2609
- d: path.stringify()
2610
- }, options.classNames.line, true).attr({
2611
- 'values': normalizedData[seriesIndex]
2612
- }, Chartist.xmlNs.uri);
2613
-
2614
- this.eventEmitter.emit('draw', {
2615
- type: 'line',
2616
- values: normalizedData[seriesIndex],
2617
- path: path.clone(),
2618
- chartRect: chartRect,
2619
- index: seriesIndex,
2620
- group: seriesGroups[seriesIndex],
2621
- element: line
2622
- });
2623
- }
3137
+ }.bind(this));
3138
+ }
2624
3139
 
2625
- if(options.showArea) {
2626
- // If areaBase is outside the chart area (< low or > high) we need to set it respectively so that
2627
- // the area is not drawn outside the chart area.
2628
- var areaBase = Math.max(Math.min(options.areaBase, axisY.bounds.max), axisY.bounds.min);
3140
+ if(seriesOptions.showLine) {
3141
+ var line = seriesElement.elem('path', {
3142
+ d: path.stringify()
3143
+ }, options.classNames.line, true);
2629
3144
 
2630
- // We project the areaBase value into screen coordinates
2631
- var areaBaseProjected = chartRect.y1 - axisY.projectValue(areaBase).pos;
3145
+ this.eventEmitter.emit('draw', {
3146
+ type: 'line',
3147
+ values: data.normalized[seriesIndex],
3148
+ path: path.clone(),
3149
+ chartRect: chartRect,
3150
+ index: seriesIndex,
3151
+ series: series,
3152
+ seriesIndex: seriesIndex,
3153
+ axisX: axisX,
3154
+ axisY: axisY,
3155
+ group: seriesElement,
3156
+ element: line
3157
+ });
3158
+ }
2632
3159
 
2633
- // Clone original path and splice our new area path to add the missing path elements to close the area shape
2634
- var areaPath = path.clone();
2635
- // Modify line path and add missing elements for area
2636
- areaPath.position(0)
3160
+ // Area currently only works with axes that support a range!
3161
+ if(seriesOptions.showArea && axisY.range) {
3162
+ // If areaBase is outside the chart area (< min or > max) we need to set it respectively so that
3163
+ // the area is not drawn outside the chart area.
3164
+ var areaBase = Math.max(Math.min(seriesOptions.areaBase, axisY.range.max), axisY.range.min);
3165
+
3166
+ // We project the areaBase value into screen coordinates
3167
+ var areaBaseProjected = chartRect.y1 - axisY.projectValue(areaBase);
3168
+
3169
+ // In order to form the area we'll first split the path by move commands so we can chunk it up into segments
3170
+ path.splitByCommand('M').filter(function onlySolidSegments(pathSegment) {
3171
+ // We filter only "solid" segments that contain more than one point. Otherwise there's no need for an area
3172
+ return pathSegment.pathElements.length > 1;
3173
+ }).map(function convertToArea(solidPathSegments) {
3174
+ // Receiving the filtered solid path segments we can now convert those segments into fill areas
3175
+ var firstElement = solidPathSegments.pathElements[0];
3176
+ var lastElement = solidPathSegments.pathElements[solidPathSegments.pathElements.length - 1];
3177
+
3178
+ // Cloning the solid path segment with closing option and removing the first move command from the clone
3179
+ // We then insert a new move that should start at the area base and draw a straight line up or down
3180
+ // at the end of the path we add an additional straight line to the projected area base value
3181
+ // As the closing option is set our path will be automatically closed
3182
+ return solidPathSegments.clone(true)
3183
+ .position(0)
2637
3184
  .remove(1)
2638
- .move(chartRect.x1, areaBaseProjected)
2639
- .line(pathCoordinates[0], pathCoordinates[1])
2640
- .position(areaPath.pathElements.length)
2641
- .line(pathCoordinates[pathCoordinates.length - 2], areaBaseProjected);
2642
-
2643
- // Create the new path for the area shape with the area class from the options
2644
- var area = seriesGroups[seriesIndex].elem('path', {
3185
+ .move(firstElement.x, areaBaseProjected)
3186
+ .line(firstElement.x, firstElement.y)
3187
+ .position(solidPathSegments.pathElements.length + 1)
3188
+ .line(lastElement.x, areaBaseProjected);
3189
+
3190
+ }).forEach(function createArea(areaPath) {
3191
+ // For each of our newly created area paths, we'll now create path elements by stringifying our path objects
3192
+ // and adding the created DOM elements to the correct series group
3193
+ var area = seriesElement.elem('path', {
2645
3194
  d: areaPath.stringify()
2646
3195
  }, options.classNames.area, true).attr({
2647
- 'values': normalizedData[seriesIndex]
3196
+ 'values': data.normalized[seriesIndex]
2648
3197
  }, Chartist.xmlNs.uri);
2649
3198
 
3199
+ // Emit an event for each area that was drawn
2650
3200
  this.eventEmitter.emit('draw', {
2651
3201
  type: 'area',
2652
- values: normalizedData[seriesIndex],
3202
+ values: data.normalized[seriesIndex],
2653
3203
  path: areaPath.clone(),
3204
+ series: series,
3205
+ seriesIndex: seriesIndex,
3206
+ axisX: axisX,
3207
+ axisY: axisY,
2654
3208
  chartRect: chartRect,
2655
3209
  index: seriesIndex,
2656
- group: seriesGroups[seriesIndex],
3210
+ group: seriesElement,
2657
3211
  element: area
2658
3212
  });
2659
- }
3213
+ }.bind(this));
2660
3214
  }
2661
3215
  }.bind(this));
2662
3216
 
@@ -2786,6 +3340,8 @@ var Chartist = {
2786
3340
  axisX: {
2787
3341
  // The offset of the chart drawing area to the border of the container
2788
3342
  offset: 30,
3343
+ // Position where labels are placed. Can be set to `start` or `end` where `start` is equivalent to left or top on vertical axis and `end` is equivalent to right or bottom on horizontal axis.
3344
+ position: 'end',
2789
3345
  // Allows you to correct label positioning on this axis by positive or negative x and y offset.
2790
3346
  labelOffset: {
2791
3347
  x: 0,
@@ -2798,12 +3354,16 @@ var Chartist = {
2798
3354
  // Interpolation function that allows you to intercept the value from the axis label
2799
3355
  labelInterpolationFnc: Chartist.noop,
2800
3356
  // This value specifies the minimum width in pixel of the scale steps
2801
- scaleMinSpace: 40
3357
+ scaleMinSpace: 30,
3358
+ // Use only integer values (whole numbers) for the scale steps
3359
+ onlyInteger: false
2802
3360
  },
2803
3361
  // Options for Y-Axis
2804
3362
  axisY: {
2805
3363
  // The offset of the chart drawing area to the border of the container
2806
3364
  offset: 40,
3365
+ // Position where labels are placed. Can be set to `start` or `end` where `start` is equivalent to left or top on vertical axis and `end` is equivalent to right or bottom on horizontal axis.
3366
+ position: 'start',
2807
3367
  // Allows you to correct label positioning on this axis by positive or negative x and y offset.
2808
3368
  labelOffset: {
2809
3369
  x: 0,
@@ -2816,7 +3376,9 @@ var Chartist = {
2816
3376
  // Interpolation function that allows you to intercept the value from the axis label
2817
3377
  labelInterpolationFnc: Chartist.noop,
2818
3378
  // This value specifies the minimum height in pixel of the scale steps
2819
- scaleMinSpace: 20
3379
+ scaleMinSpace: 20,
3380
+ // Use only integer values (whole numbers) for the scale steps
3381
+ onlyInteger: false
2820
3382
  },
2821
3383
  // Specify a fixed width for the chart as a string (i.e. '100px' or '50%')
2822
3384
  width: undefined,
@@ -2826,19 +3388,29 @@ var Chartist = {
2826
3388
  high: undefined,
2827
3389
  // Overriding the natural low of the chart allows you to zoom in or limit the charts lowest displayed value
2828
3390
  low: undefined,
3391
+ // Use only integer values (whole numbers) for the scale steps
3392
+ onlyInteger: false,
2829
3393
  // Padding of the chart drawing area to the container element and labels as a number or padding object {top: 5, right: 5, bottom: 5, left: 5}
2830
- chartPadding: 5,
3394
+ chartPadding: {
3395
+ top: 15,
3396
+ right: 15,
3397
+ bottom: 5,
3398
+ left: 10
3399
+ },
2831
3400
  // Specify the distance in pixel of bars in a group
2832
3401
  seriesBarDistance: 15,
2833
3402
  // If set to true this property will cause the series bars to be stacked and form a total for each series point. This will also influence the y-axis and the overall bounds of the chart. In stacked mode the seriesBarDistance property will have no effect.
2834
3403
  stackBars: false,
2835
3404
  // Inverts the axes of the bar chart in order to draw a horizontal bar chart. Be aware that you also need to invert your axis settings as the Y Axis will now display the labels and the X Axis the values.
2836
3405
  horizontalBars: false,
3406
+ // If set to true then each bar will represent a series and the data array is expected to be a one dimensional array of data values rather than a series array of series. This is useful if the bar chart should represent a profile rather than some data over time.
3407
+ distributeSeries: false,
2837
3408
  // If true the whole data is reversed including labels, the series order as well as the whole series data arrays.
2838
3409
  reverseData: false,
2839
3410
  // Override the class names that get used to generate the SVG structure of the chart
2840
3411
  classNames: {
2841
3412
  chart: 'ct-chart-bar',
3413
+ horizontalBars: 'ct-horizontal-bars',
2842
3414
  label: 'ct-label',
2843
3415
  labelGroup: 'ct-labels',
2844
3416
  series: 'ct-series',
@@ -2846,7 +3418,9 @@ var Chartist = {
2846
3418
  grid: 'ct-grid',
2847
3419
  gridGroup: 'ct-grids',
2848
3420
  vertical: 'ct-vertical',
2849
- horizontal: 'ct-horizontal'
3421
+ horizontal: 'ct-horizontal',
3422
+ start: 'ct-start',
3423
+ end: 'ct-end'
2850
3424
  }
2851
3425
  };
2852
3426
 
@@ -2855,23 +3429,48 @@ var Chartist = {
2855
3429
  *
2856
3430
  */
2857
3431
  function createChart(options) {
2858
- var seriesGroups = [],
2859
- normalizedData = Chartist.normalizeDataArray(Chartist.getDataArray(this.data, options.reverseData), this.data.labels.length),
2860
- normalizedPadding = Chartist.normalizePadding(options.chartPadding, defaultOptions.padding),
2861
- highLow;
3432
+ var data = {
3433
+ raw: this.data,
3434
+ normalized: options.distributeSeries ? Chartist.getDataArray(this.data, options.reverseData, options.horizontalBars ? 'x' : 'y').map(function(value) {
3435
+ return [value];
3436
+ }) : Chartist.getDataArray(this.data, options.reverseData, options.horizontalBars ? 'x' : 'y')
3437
+ };
3438
+
3439
+ var highLow;
2862
3440
 
2863
3441
  // Create new svg element
2864
- this.svg = Chartist.createSvg(this.container, options.width, options.height, options.classNames.chart);
3442
+ this.svg = Chartist.createSvg(
3443
+ this.container,
3444
+ options.width,
3445
+ options.height,
3446
+ options.classNames.chart + (options.horizontalBars ? ' ' + options.classNames.horizontalBars : '')
3447
+ );
3448
+
3449
+ // Drawing groups in correct order
3450
+ var gridGroup = this.svg.elem('g').addClass(options.classNames.gridGroup);
3451
+ var seriesGroup = this.svg.elem('g');
3452
+ var labelGroup = this.svg.elem('g').addClass(options.classNames.labelGroup);
2865
3453
 
2866
3454
  if(options.stackBars) {
2867
3455
  // If stacked bars we need to calculate the high low from stacked values from each series
2868
- var serialSums = Chartist.serialMap(normalizedData, function serialSums() {
2869
- return Array.prototype.slice.call(arguments).reduce(Chartist.sum, 0);
3456
+ var serialSums = Chartist.serialMap(data.normalized, function serialSums() {
3457
+ return Array.prototype.slice.call(arguments).map(function(value) {
3458
+ return value;
3459
+ }).reduce(function(prev, curr) {
3460
+ return {
3461
+ x: prev.x + curr.x || 0,
3462
+ y: prev.y + curr.y || 0
3463
+ };
3464
+ }, {x: 0, y: 0});
2870
3465
  });
2871
3466
 
2872
- highLow = Chartist.getHighLow([serialSums]);
3467
+ highLow = Chartist.getHighLow([serialSums], Chartist.extend({}, options, {
3468
+ referenceValue: 0
3469
+ }), options.horizontalBars ? 'x' : 'y');
2873
3470
  } else {
2874
- highLow = Chartist.getHighLow(normalizedData);
3471
+ highLow = Chartist.getHighLow(data.normalized, Chartist.extend({}, options, {
3472
+ referenceValue: 0
3473
+ }), options.horizontalBars ? 'x' : 'y');
2875
3474
  }
2876
3475
  // Overrides of high / low from settings
2877
3476
  highLow.high = +options.high || (options.high === 0 ? 0 : highLow.high);
@@ -2880,149 +3479,167 @@ var Chartist = {
2880
3479
  var chartRect = Chartist.createChartRect(this.svg, options, defaultOptions.padding);
2881
3480
 
2882
3481
  var valueAxis,
3482
+ labelAxisTicks,
2883
3483
  labelAxis,
2884
3484
  axisX,
2885
3485
  axisY;
2886
3486
 
3487
+ // We need to set step count based on some options combinations
3488
+ if(options.distributeSeries && options.stackBars) {
3489
+ // If distributed series are enabled and bars need to be stacked, we'll only have one bar and therefore should
3490
+ // use only the first label for the step axis
3491
+ labelAxisTicks = data.raw.labels.slice(0, 1);
3492
+ } else {
3493
+ // If distributed series are enabled but stacked bars aren't, we should use the series labels
3494
+ // If we are drawing a regular bar chart with two dimensional series data, we just use the labels array
3495
+ // as the bars are normalized
3496
+ labelAxisTicks = data.raw.labels;
3497
+ }
3498
+
3499
+ // Set labelAxis and valueAxis based on the horizontalBars setting. This setting will flip the axes if necessary.
2887
3500
  if(options.horizontalBars) {
2888
- labelAxis = axisY = new Chartist.StepAxis(
2889
- Chartist.Axis.units.y,
2890
- chartRect,
2891
- function timeAxisTransform(projectedValue) {
2892
- projectedValue.pos = chartRect.y1 - projectedValue.pos;
2893
- return projectedValue;
2894
- },
2895
- {
2896
- x: normalizedPadding.left + options.axisY.labelOffset.x + (this.supportsForeignObject ? -10 : 0),
2897
- y: options.axisY.labelOffset.y - chartRect.height() / this.data.labels.length
2898
- },
2899
- {
2900
- stepCount: this.data.labels.length,
2901
- stretch: options.fullHeight
2902
- }
2903
- );
2904
-
2905
- valueAxis = axisX = new Chartist.LinearScaleAxis(
2906
- Chartist.Axis.units.x,
2907
- chartRect,
2908
- function valueAxisTransform(projectedValue) {
2909
- projectedValue.pos = chartRect.x1 + projectedValue.pos;
2910
- return projectedValue;
2911
- },
2912
- {
2913
- x: options.axisX.labelOffset.x,
2914
- y: chartRect.y1 + options.axisX.labelOffset.y + (this.supportsForeignObject ? 5 : 20)
2915
- },
2916
- {
3501
+ if(options.axisX.type === undefined) {
3502
+ valueAxis = axisX = new Chartist.AutoScaleAxis(Chartist.Axis.units.x, data, chartRect, Chartist.extend({}, options.axisX, {
2917
3503
  highLow: highLow,
2918
- scaleMinSpace: options.axisX.scaleMinSpace,
2919
3504
  referenceValue: 0
2920
- }
2921
- );
3505
+ }));
3506
+ } else {
3507
+ valueAxis = axisX = options.axisX.type.call(Chartist, Chartist.Axis.units.x, data, chartRect, Chartist.extend({}, options.axisX, {
3508
+ highLow: highLow,
3509
+ referenceValue: 0
3510
+ }));
3511
+ }
3512
+
3513
+ if(options.axisY.type === undefined) {
3514
+ labelAxis = axisY = new Chartist.StepAxis(Chartist.Axis.units.y, data, chartRect, {
3515
+ ticks: labelAxisTicks
3516
+ });
3517
+ } else {
3518
+ labelAxis = axisY = options.axisY.type.call(Chartist, Chartist.Axis.units.y, data, chartRect, options.axisY);
3519
+ }
2922
3520
  } else {
2923
- labelAxis = axisX = new Chartist.StepAxis(
2924
- Chartist.Axis.units.x,
2925
- chartRect,
2926
- function timeAxisTransform(projectedValue) {
2927
- projectedValue.pos = chartRect.x1 + projectedValue.pos;
2928
- return projectedValue;
2929
- },
2930
- {
2931
- x: options.axisX.labelOffset.x,
2932
- y: chartRect.y1 + options.axisX.labelOffset.y + (this.supportsForeignObject ? 5 : 20)
2933
- },
2934
- {
2935
- stepCount: this.data.labels.length
2936
- }
2937
- );
2938
-
2939
- valueAxis = axisY = new Chartist.LinearScaleAxis(
2940
- Chartist.Axis.units.y,
2941
- chartRect,
2942
- function valueAxisTransform(projectedValue) {
2943
- projectedValue.pos = chartRect.y1 - projectedValue.pos;
2944
- return projectedValue;
2945
- },
2946
- {
2947
- x: normalizedPadding.left + options.axisY.labelOffset.x + (this.supportsForeignObject ? -10 : 0),
2948
- y: options.axisY.labelOffset.y + (this.supportsForeignObject ? -15 : 0)
2949
- },
2950
- {
3521
+ if(options.axisX.type === undefined) {
3522
+ labelAxis = axisX = new Chartist.StepAxis(Chartist.Axis.units.x, data, chartRect, {
3523
+ ticks: labelAxisTicks
3524
+ });
3525
+ } else {
3526
+ labelAxis = axisX = options.axisX.type.call(Chartist, Chartist.Axis.units.x, data, chartRect, options.axisX);
3527
+ }
3528
+
3529
+ if(options.axisY.type === undefined) {
3530
+ valueAxis = axisY = new Chartist.AutoScaleAxis(Chartist.Axis.units.y, data, chartRect, Chartist.extend({}, options.axisY, {
2951
3531
  highLow: highLow,
2952
- scaleMinSpace: options.axisY.scaleMinSpace,
2953
3532
  referenceValue: 0
2954
- }
2955
- );
3533
+ }));
3534
+ } else {
3535
+ valueAxis = axisY = options.axisY.type.call(Chartist, Chartist.Axis.units.y, data, chartRect, Chartist.extend({}, options.axisY, {
3536
+ highLow: highLow,
3537
+ referenceValue: 0
3538
+ }));
3539
+ }
2956
3540
  }
2957
3541
 
2958
- // Start drawing
2959
- var labelGroup = this.svg.elem('g').addClass(options.classNames.labelGroup),
2960
- gridGroup = this.svg.elem('g').addClass(options.classNames.gridGroup),
2961
- // Projected 0 point
2962
- zeroPoint = options.horizontalBars ? (chartRect.x1 + valueAxis.projectValue(0).pos) : (chartRect.y1 - valueAxis.projectValue(0).pos),
2963
- // Used to track the screen coordinates of stacked bars
2964
- stackedBarValues = [];
3542
+ // Projected 0 point
3543
+ var zeroPoint = options.horizontalBars ? (chartRect.x1 + valueAxis.projectValue(0)) : (chartRect.y1 - valueAxis.projectValue(0));
3544
+ // Used to track the screen coordinates of stacked bars
3545
+ var stackedBarValues = [];
2965
3546
 
2966
- Chartist.createAxis(
2967
- labelAxis,
2968
- this.data.labels,
2969
- chartRect,
2970
- gridGroup,
2971
- labelGroup,
2972
- this.supportsForeignObject,
2973
- options,
2974
- this.eventEmitter
2975
- );
2976
-
2977
- Chartist.createAxis(
2978
- valueAxis,
2979
- valueAxis.bounds.values,
2980
- chartRect,
2981
- gridGroup,
2982
- labelGroup,
2983
- this.supportsForeignObject,
2984
- options,
2985
- this.eventEmitter
2986
- );
3547
+ labelAxis.createGridAndLabels(gridGroup, labelGroup, this.supportsForeignObject, options, this.eventEmitter);
3548
+ valueAxis.createGridAndLabels(gridGroup, labelGroup, this.supportsForeignObject, options, this.eventEmitter);
2987
3549
 
2988
3550
  // Draw the series
2989
- this.data.series.forEach(function(series, seriesIndex) {
3551
+ data.raw.series.forEach(function(series, seriesIndex) {
2990
3552
  // Calculating bi-polar value of index for seriesOffset. For i = 0..4 biPol will be -1.5, -0.5, 0.5, 1.5 etc.
2991
- var biPol = seriesIndex - (this.data.series.length - 1) / 2,
3553
+ var biPol = seriesIndex - (data.raw.series.length - 1) / 2;
2992
3554
  // Half of the period width between vertical grid lines used to position bars
2993
- periodHalfLength = chartRect[labelAxis.units.len]() / normalizedData[seriesIndex].length / 2;
3555
+ var periodHalfLength;
3556
+ // Current series SVG element
3557
+ var seriesElement;
3558
+
3559
+ // We need to set periodHalfLength based on some options combinations
3560
+ if(options.distributeSeries && !options.stackBars) {
3561
+ // If distributed series are enabled but stacked bars aren't, we need to use the length of the normaizedData array
3562
+ // which is the series count and divide by 2
3563
+ periodHalfLength = labelAxis.axisLength / data.normalized.length / 2;
3564
+ } else if(options.distributeSeries && options.stackBars) {
3565
+ // If distributed series and stacked bars are enabled we'll only get one bar so we should just divide the axis
3566
+ // length by 2
3567
+ periodHalfLength = labelAxis.axisLength / 2;
3568
+ } else {
3569
+ // On regular bar charts we should just use the series length
3570
+ periodHalfLength = labelAxis.axisLength / data.normalized[seriesIndex].length / 2;
3571
+ }
2994
3572
 
2995
- seriesGroups[seriesIndex] = this.svg.elem('g');
3573
+ // Adding the series group to the series element
3574
+ seriesElement = seriesGroup.elem('g');
2996
3575
 
2997
3576
  // Write attributes to series group element. If series name or meta is undefined the attributes will not be written
2998
- seriesGroups[seriesIndex].attr({
3577
+ seriesElement.attr({
2999
3578
  'series-name': series.name,
3000
3579
  'meta': Chartist.serialize(series.meta)
3001
3580
  }, Chartist.xmlNs.uri);
3002
3581
 
3003
3582
  // Use series class from series data or if not set generate one
3004
- seriesGroups[seriesIndex].addClass([
3583
+ seriesElement.addClass([
3005
3584
  options.classNames.series,
3006
3585
  (series.className || options.classNames.series + '-' + Chartist.alphaNumerate(seriesIndex))
3007
3586
  ].join(' '));
3008
3587
 
3009
- normalizedData[seriesIndex].forEach(function(value, valueIndex) {
3010
- var projected = {
3011
- x: chartRect.x1 + (options.horizontalBars ? valueAxis : labelAxis).projectValue(value, valueIndex, normalizedData[seriesIndex]).pos,
3012
- y: chartRect.y1 - (options.horizontalBars ? labelAxis : valueAxis).projectValue(value, valueIndex, normalizedData[seriesIndex]).pos
3013
- },
3588
+ data.normalized[seriesIndex].forEach(function(value, valueIndex) {
3589
+ var projected,
3014
3590
  bar,
3015
- previousStack;
3591
+ previousStack,
3592
+ labelAxisValueIndex;
3593
+
3594
+ // We need to set labelAxisValueIndex based on some options combinations
3595
+ if(options.distributeSeries && !options.stackBars) {
3596
+ // If distributed series are enabled but stacked bars aren't, we can use the seriesIndex for later projection
3597
+ // on the step axis for label positioning
3598
+ labelAxisValueIndex = seriesIndex;
3599
+ } else if(options.distributeSeries && options.stackBars) {
3600
+ // If distributed series and stacked bars are enabled, we will only get one bar and therefore always use
3601
+ // 0 for projection on the label step axis
3602
+ labelAxisValueIndex = 0;
3603
+ } else {
3604
+ // On regular bar charts we just use the value index to project on the label step axis
3605
+ labelAxisValueIndex = valueIndex;
3606
+ }
3607
+
3608
+ // We need to transform coordinates differently based on the chart layout
3609
+ if(options.horizontalBars) {
3610
+ projected = {
3611
+ x: chartRect.x1 + valueAxis.projectValue(value && value.x ? value.x : 0, valueIndex, data.normalized[seriesIndex]),
3612
+ y: chartRect.y1 - labelAxis.projectValue(value && value.y ? value.y : 0, labelAxisValueIndex, data.normalized[seriesIndex])
3613
+ };
3614
+ } else {
3615
+ projected = {
3616
+ x: chartRect.x1 + labelAxis.projectValue(value && value.x ? value.x : 0, labelAxisValueIndex, data.normalized[seriesIndex]),
3617
+ y: chartRect.y1 - valueAxis.projectValue(value && value.y ? value.y : 0, valueIndex, data.normalized[seriesIndex])
3618
+ }
3619
+ }
3016
3620
 
3017
- // Offset to center bar between grid lines
3018
- projected[labelAxis.units.pos] += periodHalfLength * (options.horizontalBars ? -1 : 1);
3019
- // Using bi-polar offset for multiple series if no stacked bars are used
3020
- projected[labelAxis.units.pos] += options.stackBars ? 0 : biPol * options.seriesBarDistance * (options.horizontalBars ? -1 : 1);
3621
+ // If the label axis is a step based axis we will offset the bar into the middle of between two steps using
3622
+ // the periodHalfLength value. Also we do arrange the different series so that they align up to each other using
3623
+ // the seriesBarDistance. If we don't have a step axis, the bar positions can be chosen freely so we should not
3624
+ // add any automated positioning.
3625
+ if(labelAxis instanceof Chartist.StepAxis) {
3626
+ // Offset to center bar between grid lines, but only if the step axis is not stretched
3627
+ if(!labelAxis.options.stretch) {
3628
+ projected[labelAxis.units.pos] += periodHalfLength * (options.horizontalBars ? -1 : 1);
3629
+ }
3630
+ // Using bi-polar offset for multiple series if no stacked bars or series distribution is used
3631
+ projected[labelAxis.units.pos] += (options.stackBars || options.distributeSeries) ? 0 : biPol * options.seriesBarDistance * (options.horizontalBars ? -1 : 1);
3632
+ }
3021
3633
 
3022
3634
  // Enter value in stacked bar values used to remember previous screen value for stacking up bars
3023
3635
  previousStack = stackedBarValues[valueIndex] || zeroPoint;
3024
3636
  stackedBarValues[valueIndex] = previousStack - (zeroPoint - projected[labelAxis.counterUnits.pos]);
3025
3637
 
3638
+ // Skip if value is undefined
3639
+ if(value === undefined) {
3640
+ return;
3641
+ }
3642
+
3026
3643
  var positions = {};
3027
3644
  positions[labelAxis.units.pos + '1'] = projected[labelAxis.units.pos];
3028
3645
  positions[labelAxis.units.pos + '2'] = projected[labelAxis.units.pos];
@@ -3030,8 +3647,17 @@ var Chartist = {
3030
3647
  positions[labelAxis.counterUnits.pos + '1'] = options.stackBars ? previousStack : zeroPoint;
3031
3648
  positions[labelAxis.counterUnits.pos + '2'] = options.stackBars ? stackedBarValues[valueIndex] : projected[labelAxis.counterUnits.pos];
3032
3649
 
3033
- bar = seriesGroups[seriesIndex].elem('line', positions, options.classNames.bar).attr({
3034
- 'value': value,
3650
+ // Limit x and y so that they are within the chart rect
3651
+ positions.x1 = Math.min(Math.max(positions.x1, chartRect.x1), chartRect.x2);
3652
+ positions.x2 = Math.min(Math.max(positions.x2, chartRect.x1), chartRect.x2);
3653
+ positions.y1 = Math.min(Math.max(positions.y1, chartRect.y2), chartRect.y1);
3654
+ positions.y2 = Math.min(Math.max(positions.y2, chartRect.y2), chartRect.y1);
3655
+
3656
+ // Create bar element
3657
+ bar = seriesElement.elem('line', positions, options.classNames.bar).attr({
3658
+ 'value': [value.x, value.y].filter(function(v) {
3659
+ return v;
3660
+ }).join(','),
3035
3661
  'meta': Chartist.getMetaData(series, valueIndex)
3036
3662
  }, Chartist.xmlNs.uri);
3037
3663
 
@@ -3039,8 +3665,13 @@ var Chartist = {
3039
3665
  type: 'bar',
3040
3666
  value: value,
3041
3667
  index: valueIndex,
3668
+ meta: Chartist.getMetaData(series, valueIndex),
3669
+ series: series,
3670
+ seriesIndex: seriesIndex,
3671
+ axisX: axisX,
3672
+ axisY: axisY,
3042
3673
  chartRect: chartRect,
3043
- group: seriesGroups[seriesIndex],
3674
+ group: seriesElement,
3044
3675
  element: bar
3045
3676
  }, positions));
3046
3677
  }.bind(this));
@@ -3130,12 +3761,13 @@ var Chartist = {
3130
3761
  height: undefined,
3131
3762
  // Padding of the chart drawing area to the container element and labels as a number or padding object {top: 5, right: 5, bottom: 5, left: 5}
3132
3763
  chartPadding: 5,
3133
- // Override the class names that get used to generate the SVG structure of the chart
3764
+ // Override the class names that are used to generate the SVG structure of the chart
3134
3765
  classNames: {
3135
- chart: 'ct-chart-pie',
3766
+ chartPie: 'ct-chart-pie',
3767
+ chartDonut: 'ct-chart-donut',
3136
3768
  series: 'ct-series',
3137
- slice: 'ct-slice',
3138
- donut: 'ct-donut',
3769
+ slicePie: 'ct-slice-pie',
3770
+ sliceDonut: 'ct-slice-donut',
3139
3771
  label: 'ct-label'
3140
3772
  },
3141
3773
  // The start angle of the pie chart in degrees where 0 points north. A higher value offsets the start angle clockwise.
@@ -3150,6 +3782,8 @@ var Chartist = {
3150
3782
  showLabel: true,
3151
3783
  // Label position offset from the standard position which is half distance of the radius. This value can be either positive or negative. Positive values will position the label away from the center.
3152
3784
  labelOffset: 0,
3785
+ // This option can be set to 'inside', 'outside' or 'center'. Positioned with 'inside' the labels will be placed on half the distance of the radius to the border of the Pie by respecting the 'labelOffset'. The 'outside' option will place the labels at the border of the pie and 'center' will place the labels in the absolute center point of the chart. The 'center' option only makes sense in conjunction with the 'labelOffset' option.
3786
+ labelPosition: 'inside',
3153
3787
  // An interpolation function for the label value
3154
3788
  labelInterpolationFnc: Chartist.noop,
3155
3789
  // Label direction can be 'neutral', 'explode' or 'implode'. The labels anchor will be positioned based on those settings as well as the fact if the labels are on the right or left side of the center of the chart. Usually explode is useful when labels are positioned far away from the center.
@@ -3187,6 +3821,7 @@ var Chartist = {
3187
3821
  */
3188
3822
  function createChart(options) {
3189
3823
  var seriesGroups = [],
3824
+ labelsGroup,
3190
3825
  chartRect,
3191
3826
  radius,
3192
3827
  labelRadius,
@@ -3195,7 +3830,7 @@ var Chartist = {
3195
3830
  dataArray = Chartist.getDataArray(this.data, options.reverseData);
3196
3831
 
3197
3832
  // Create SVG.js draw
3198
- this.svg = Chartist.createSvg(this.container, options.width, options.height, options.classNames.chart);
3833
+ this.svg = Chartist.createSvg(this.container, options.width, options.height,options.donut ? options.classNames.chartDonut : options.classNames.chartPie);
3199
3834
  // Calculate charting rect
3200
3835
  chartRect = Chartist.createChartRect(this.svg, options, defaultOptions.padding);
3201
3836
  // Get biggest circle radius possible within chartRect
@@ -3210,9 +3845,18 @@ var Chartist = {
3210
3845
  // See this proposal for more details: http://lists.w3.org/Archives/Public/www-svg/2003Oct/0000.html
3211
3846
  radius -= options.donut ? options.donutWidth / 2 : 0;
3212
3847
 
3213
- // If a donut chart then the label position is at the radius, if regular pie chart it's half of the radius
3214
- // see https://github.com/gionkunz/chartist-js/issues/21
3215
- labelRadius = options.donut ? radius : radius / 2;
3848
+ // If labelPosition is set to `outside` or a donut chart is drawn then the label position is at the radius,
3849
+ // if regular pie chart it's half of the radius
3850
+ if(options.labelPosition === 'outside' || options.donut) {
3851
+ labelRadius = radius;
3852
+ } else if(options.labelPosition === 'center') {
3853
+ // If labelPosition is center we start with 0 and will later wait for the labelOffset
3854
+ labelRadius = 0;
3855
+ } else {
3856
+ // Default option is 'inside' where we use half the radius so the label will be placed in the center of the pie
3857
+ // slice
3858
+ labelRadius = radius / 2;
3859
+ }
3216
3860
  // Add the offset to the labelRadius where a negative offset means closed to the center of the chart
3217
3861
  labelRadius += options.labelOffset;
3218
3862
 
@@ -3224,26 +3868,29 @@ var Chartist = {
3224
3868
 
3225
3869
  // Check if there is only one non-zero value in the series array.
3226
3870
  var hasSingleValInSeries = this.data.series.filter(function(val) {
3227
- return val !== 0;
3871
+ return val.hasOwnProperty('value') ? val.value !== 0 : val !== 0;
3228
3872
  }).length === 1;
3229
3873
 
3874
+ //if we need to show labels we create the label group now
3875
+ if(options.showLabel) {
3876
+ labelsGroup = this.svg.elem('g', null, null, true);
3877
+ }
3878
+
3230
3879
  // Draw the series
3231
3880
  // initialize series groups
3232
3881
  for (var i = 0; i < this.data.series.length; i++) {
3882
+ var series = this.data.series[i];
3233
3883
  seriesGroups[i] = this.svg.elem('g', null, null, true);
3234
3884
 
3235
- // If the series is an object and contains a name we add a custom attribute
3236
- if(this.data.series[i].name) {
3237
- seriesGroups[i].attr({
3238
- 'series-name': this.data.series[i].name,
3239
- 'meta': Chartist.serialize(this.data.series[i].meta)
3240
- }, Chartist.xmlNs.uri);
3241
- }
3885
+ // If the series is an object and contains a name or meta data we add a custom attribute
3886
+ seriesGroups[i].attr({
3887
+ 'series-name': series.name
3888
+ }, Chartist.xmlNs.uri);
3242
3889
 
3243
3890
  // Use series class from series data or if not set generate one
3244
3891
  seriesGroups[i].addClass([
3245
3892
  options.classNames.series,
3246
- (this.data.series[i].className || options.classNames.series + '-' + Chartist.alphaNumerate(i))
3893
+ (series.className || options.classNames.series + '-' + Chartist.alphaNumerate(i))
3247
3894
  ].join(' '));
3248
3895
 
3249
3896
  var endAngle = startAngle + dataArray[i] / totalDataSum * 360;
@@ -3270,11 +3917,12 @@ var Chartist = {
3270
3917
  // If this is a donut chart we add the donut class, otherwise just a regular slice
3271
3918
  var pathElement = seriesGroups[i].elem('path', {
3272
3919
  d: path.stringify()
3273
- }, options.classNames.slice + (options.donut ? ' ' + options.classNames.donut : ''));
3920
+ }, options.donut ? options.classNames.sliceDonut : options.classNames.slicePie);
3274
3921
 
3275
3922
  // Adding the pie series value to the path
3276
3923
  pathElement.attr({
3277
- 'value': dataArray[i]
3924
+ 'value': dataArray[i],
3925
+ 'meta': Chartist.serialize(series.meta)
3278
3926
  }, Chartist.xmlNs.uri);
3279
3927
 
3280
3928
  // If this is a donut, we add the stroke-width as style attribute
@@ -3290,6 +3938,8 @@ var Chartist = {
3290
3938
  value: dataArray[i],
3291
3939
  totalDataSum: totalDataSum,
3292
3940
  index: i,
3941
+ meta: series.meta,
3942
+ series: series,
3293
3943
  group: seriesGroups[i],
3294
3944
  element: pathElement,
3295
3945
  path: path.clone(),
@@ -3305,22 +3955,24 @@ var Chartist = {
3305
3955
  var labelPosition = Chartist.polarToCartesian(center.x, center.y, labelRadius, startAngle + (endAngle - startAngle) / 2),
3306
3956
  interpolatedValue = options.labelInterpolationFnc(this.data.labels ? this.data.labels[i] : dataArray[i], i);
3307
3957
 
3308
- var labelElement = seriesGroups[i].elem('text', {
3309
- dx: labelPosition.x,
3310
- dy: labelPosition.y,
3311
- 'text-anchor': determineAnchorPosition(center, labelPosition, options.labelDirection)
3312
- }, options.classNames.label).text('' + interpolatedValue);
3958
+ if(interpolatedValue || interpolatedValue === 0) {
3959
+ var labelElement = labelsGroup.elem('text', {
3960
+ dx: labelPosition.x,
3961
+ dy: labelPosition.y,
3962
+ 'text-anchor': determineAnchorPosition(center, labelPosition, options.labelDirection)
3963
+ }, options.classNames.label).text('' + interpolatedValue);
3313
3964
 
3314
- // Fire off draw event
3315
- this.eventEmitter.emit('draw', {
3316
- type: 'label',
3317
- index: i,
3318
- group: seriesGroups[i],
3319
- element: labelElement,
3320
- text: '' + interpolatedValue,
3321
- x: labelPosition.x,
3322
- y: labelPosition.y
3323
- });
3965
+ // Fire off draw event
3966
+ this.eventEmitter.emit('draw', {
3967
+ type: 'label',
3968
+ index: i,
3969
+ group: labelsGroup,
3970
+ element: labelElement,
3971
+ text: '' + interpolatedValue,
3972
+ x: labelPosition.x,
3973
+ y: labelPosition.y
3974
+ });
3975
+ }
3324
3976
  }
3325
3977
 
3326
3978
  // Set next startAngle to current endAngle. Use slight offset so there are no transparent hairline issues
@@ -3340,7 +3992,7 @@ var Chartist = {
3340
3992
  *
3341
3993
  * @memberof Chartist.Pie
3342
3994
  * @param {String|Node} query A selector query string or directly a DOM element
3343
- * @param {Object} data The data object in the pie chart needs to have a series property with a one dimensional data array. The values will be normalized against each other and don't necessarily need to be in percentage. The series property can also be an array of objects that contain a data property with the value and a className property to override the CSS class name for the series group.
3995
+ * @param {Object} data The data object in the pie chart needs to have a series property with a one dimensional data array. The values will be normalized against each other and don't necessarily need to be in percentage. The series property can also be an array of value objects that contain a value property and a className property to override the CSS class name for the series group.
3344
3996
  * @param {Object} [options] The options object with options that override the default options. Check the examples for a detailed list.
3345
3997
  * @param {Array} [responsiveOptions] Specify an array of responsive option arrays which are a media query and options object pair => [[mediaQueryString, optionsObject],[more...]]
3346
3998
  * @return {Object} An object with a version and an update method to manually redraw the chart
@@ -3381,17 +4033,25 @@ var Chartist = {
3381
4033
  * });
3382
4034
  *
3383
4035
  * @example
3384
- * // Overriding the class names for individual series
4036
+ * // Overriding the class names for individual series as well as a name and meta data.
4037
+ * // The name will be written as ct:series-name attribute and the meta data will be serialized and written
4038
+ * // to a ct:meta attribute.
3385
4039
  * new Chartist.Pie('.ct-chart', {
3386
4040
  * series: [{
3387
- * data: 20,
3388
- * className: 'my-custom-class-one'
4041
+ * value: 20,
4042
+ * name: 'Series 1',
4043
+ * className: 'my-custom-class-one',
4044
+ * meta: 'Meta One'
3389
4045
  * }, {
3390
- * data: 10,
3391
- * className: 'my-custom-class-two'
4046
+ * value: 10,
4047
+ * name: 'Series 2',
4048
+ * className: 'my-custom-class-two',
4049
+ * meta: 'Meta Two'
3392
4050
  * }, {
3393
- * data: 70,
3394
- * className: 'my-custom-class-three'
4051
+ * value: 70,
4052
+ * name: 'Series 3',
4053
+ * className: 'my-custom-class-three',
4054
+ * meta: 'Meta Three'
3395
4055
  * }]
3396
4056
  * });
3397
4057
  */