novus-nvd3-rails 1.8.2 → 1.8.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 57a3aaed9b38c413221cca11bdedaf298062321e
4
- data.tar.gz: 7287d08610e30fa6bd9b1643dbcd6d4abc967e9b
3
+ metadata.gz: fd609fdf8f388b5244547d270b2f5cb244e7ad2b
4
+ data.tar.gz: 5a954a6949b7fefe57819aa489330d36e34d693f
5
5
  SHA512:
6
- metadata.gz: f64d277e12ed0feed64d8e88d3a47881d10209065fe43149b3b9fa960b55521926238652bb42be7840f9d23724b9a59ef7480658cf721d4f09bc3f2b33286918
7
- data.tar.gz: 9a16ff71f4b064ce4c105657af8a973d1b0de279a74750c0388449e2b82cfa03d77f664cfd145e2f6397eb64cc8afcc10ab3544ad7e74f512bf1838e9dbed6c0
6
+ metadata.gz: fe70dd05dce10fca74e1996cc6188f9b968755455a8a2472b0f0af81b8a11be7f361b95f18ec8eb03012b0d01c4fba21d3f4e2e65ae7c6afa50ad68c20a9d893
7
+ data.tar.gz: 6c2ecaa4a7eef83d6a32cb422125c449d589af58a079907d415905811be280d549be4e531c0dc10db51c43596ad4c30764e6777c3d4bdd328f4415d963f0d69d
data/README.md CHANGED
@@ -36,3 +36,7 @@ https://nvd3-community.github.io/nvd3
36
36
  3. Commit your changes (`git commit -am 'Add some feature'`)
37
37
  4. Push to the branch (`git push origin my-new-feature`)
38
38
  5. Create new Pull Request
39
+
40
+ ## License
41
+
42
+ Released under the MIT License.
@@ -1,7 +1,7 @@
1
1
  module Novus
2
2
  module Nvd3
3
3
  module Rails
4
- VERSION = "1.8.2"
4
+ VERSION = "1.8.3"
5
5
  end
6
6
  end
7
7
  end
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.7"
22
22
  spec.add_development_dependency "rake", "~> 10.0"
23
- spec.add_development_dependency 'rails', '4.2.5'
23
+ spec.add_development_dependency 'rails', '4.2.6'
24
24
  spec.add_development_dependency 'rspec-rails', '~> 3.0'
25
25
  spec.add_development_dependency 'capybara', '~> 2.6'
26
26
  spec.add_development_dependency 'sqlite3', '~> 1.3'
@@ -1,4 +1,4 @@
1
- /* nvd3 version 1.8.2 (https://github.com/novus/nvd3) 2016-01-24 */
1
+ /* nvd3 version 1.8.3 (https://github.com/novus/nvd3) 2016-04-26 */
2
2
  (function(){
3
3
 
4
4
  // set up main nv object
@@ -13,6 +13,11 @@ nv.charts = {}; //stores all the ready to use charts
13
13
  nv.logs = {}; //stores some statistics and potential error messages
14
14
  nv.dom = {}; //DOM manipulation functions
15
15
 
16
+ // Node/CommonJS - require D3
17
+ if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined' && typeof(d3) == 'undefined') {
18
+ d3 = require('d3');
19
+ }
20
+
16
21
  nv.dispatch = d3.dispatch('render_start', 'render_end');
17
22
 
18
23
  // Function bind polyfill
@@ -157,7 +162,7 @@ if (typeof(window) !== 'undefined') {
157
162
  */
158
163
  nv.dom.write = function(callback) {
159
164
  if (window.fastdom !== undefined) {
160
- return fastdom.write(callback);
165
+ return fastdom.mutate(callback);
161
166
  }
162
167
  return callback();
163
168
  };
@@ -170,10 +175,11 @@ nv.dom.write = function(callback) {
170
175
  */
171
176
  nv.dom.read = function(callback) {
172
177
  if (window.fastdom !== undefined) {
173
- return fastdom.read(callback);
178
+ return fastdom.measure(callback);
174
179
  }
175
180
  return callback();
176
- };/* Utility class to handle creation of an interactive layer.
181
+ };
182
+ /* Utility class to handle creation of an interactive layer.
177
183
  This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch
178
184
  containing the X-coordinate. It can also render a vertical line where the mouse is located.
179
185
 
@@ -258,7 +264,8 @@ nv.interactiveGuideline = function() {
258
264
  /* If mouseX/Y is outside of the chart's bounds,
259
265
  trigger a mouseOut event.
260
266
  */
261
- if (mouseX < 0 || mouseY < 0
267
+ if (d3.event.type === 'mouseout'
268
+ || mouseX < 0 || mouseY < 0
262
269
  || mouseX > availableWidth || mouseY > availableHeight
263
270
  || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
264
271
  || mouseOutAnyReason
@@ -642,11 +649,11 @@ nv.models.tooltip = function() {
642
649
 
643
650
  var dataSeriesExists = function(d) {
644
651
  if (d && d.series) {
645
- if (d.series instanceof Array) {
646
- return !!d.series.length;
652
+ if (nv.utils.isArray(d.series)) {
653
+ return true;
647
654
  }
648
655
  // if object, it's okay just convert to array of the object
649
- if (d.series instanceof Object) {
656
+ if (nv.utils.isObject(d.series)) {
650
657
  d.series = [d.series];
651
658
  return true;
652
659
  }
@@ -754,18 +761,23 @@ nv.models.tooltip = function() {
754
761
 
755
762
  // Creates new tooltip container, or uses existing one on DOM.
756
763
  function initTooltip() {
757
- if (!tooltip) {
764
+ if (!tooltip || !tooltip.node()) {
758
765
  var container = chartContainer ? chartContainer : document.body;
759
-
760
766
  // Create new tooltip div if it doesn't exist on DOM.
761
- tooltip = d3.select(container).append("div")
762
- .attr("class", "nvtooltip " + (classes ? classes : "xy-tooltip"))
763
- .attr("id", id);
764
- tooltip.style("top", 0).style("left", 0);
765
- tooltip.style('opacity', 0);
766
- tooltip.style('position', 'fixed');
767
- tooltip.selectAll("div, table, td, tr").classed(nvPointerEventsClass, true);
768
- tooltip.classed(nvPointerEventsClass, true);
767
+
768
+ var data = [1];
769
+ tooltip = d3.select(container).selectAll('.nvtooltip').data(data);
770
+
771
+ tooltip.enter().append('div')
772
+ .attr("class", "nvtooltip " + (classes ? classes : "xy-tooltip"))
773
+ .attr("id", id)
774
+ .style("top", 0).style("left", 0)
775
+ .style('opacity', 0)
776
+ .style('position', 'fixed')
777
+ .selectAll("div, table, td, tr").classed(nvPointerEventsClass, true)
778
+ .classed(nvPointerEventsClass, true);
779
+
780
+ tooltip.exit().remove()
769
781
  }
770
782
  }
771
783
 
@@ -884,6 +896,25 @@ nv.utils.windowSize = function() {
884
896
  return (size);
885
897
  };
886
898
 
899
+
900
+ /* handle dumb browser quirks... isinstance breaks if you use frames
901
+ typeof returns 'object' for null, NaN is a number, etc.
902
+ */
903
+ nv.utils.isArray = Array.isArray;
904
+ nv.utils.isObject = function(a) {
905
+ return a !== null && typeof a === 'object';
906
+ };
907
+ nv.utils.isFunction = function(a) {
908
+ return typeof a === 'function';
909
+ };
910
+ nv.utils.isDate = function(a) {
911
+ return toString.call(a) === '[object Date]';
912
+ };
913
+ nv.utils.isNumber = function(a) {
914
+ return !isNaN(a) && typeof a === 'number';
915
+ };
916
+
917
+
887
918
  /*
888
919
  Binds callback function to run when window is resized
889
920
  */
@@ -915,8 +946,7 @@ nv.utils.getColor = function(color) {
915
946
  return nv.utils.defaultColor();
916
947
 
917
948
  //if passed an array, turn it into a color scale
918
- // use isArray, instanceof fails if d3 range is created in an iframe
919
- } else if(Array.isArray(color)) {
949
+ } else if(nv.utils.isArray(color)) {
920
950
  var color_scale = d3.scale.ordinal().range(color);
921
951
  return function(d, i) {
922
952
  var key = i === undefined ? d : i;
@@ -956,7 +986,7 @@ nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
956
986
 
957
987
  return function(series, index) {
958
988
  var key = getKey(series);
959
- if (typeof dictionary[key] === 'function') {
989
+ if (nv.utils.isFunction(dictionary[key])) {
960
990
  return dictionary[key]();
961
991
  } else if (dictionary[key] !== undefined) {
962
992
  return dictionary[key];
@@ -1010,12 +1040,10 @@ Most common instance is when the element is in a display:none; container.
1010
1040
  Forumla is : text.length * font-size * constant_factor
1011
1041
  */
1012
1042
  nv.utils.calcApproxTextWidth = function (svgTextElem) {
1013
- if (typeof svgTextElem.style === 'function'
1014
- && typeof svgTextElem.text === 'function') {
1015
-
1043
+ if (nv.utils.isFunction(svgTextElem.style) && nv.utils.isFunction(svgTextElem.text)) {
1016
1044
  var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""), 10);
1017
1045
  var textLength = svgTextElem.text().length;
1018
- return textLength * fontSize * 0.5;
1046
+ return nv.utils.NaNtoZero(textLength * fontSize * 0.5);
1019
1047
  }
1020
1048
  return 0;
1021
1049
  };
@@ -1025,7 +1053,7 @@ nv.utils.calcApproxTextWidth = function (svgTextElem) {
1025
1053
  Numbers that are undefined, null or NaN, convert them to zeros.
1026
1054
  */
1027
1055
  nv.utils.NaNtoZero = function(n) {
1028
- if (typeof n !== 'number'
1056
+ if (!nv.utils.isNumber(n)
1029
1057
  || isNaN(n)
1030
1058
  || n === null
1031
1059
  || n === Infinity
@@ -1143,9 +1171,9 @@ nv.utils.deepExtend = function(dst){
1143
1171
  var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
1144
1172
  sources.forEach(function(source) {
1145
1173
  for (var key in source) {
1146
- var isArray = dst[key] instanceof Array;
1147
- var isObject = typeof dst[key] === 'object';
1148
- var srcObj = typeof source[key] === 'object';
1174
+ var isArray = nv.utils.isArray(dst[key]);
1175
+ var isObject = nv.utils.isObject(dst[key]);
1176
+ var srcObj = nv.utils.isObject(source[key]);
1149
1177
 
1150
1178
  if (isObject && !isArray && srcObj) {
1151
1179
  nv.utils.deepExtend(dst[key], source[key]);
@@ -1244,7 +1272,7 @@ chart.options = nv.utils.optionsFunc.bind(chart);
1244
1272
  nv.utils.optionsFunc = function(args) {
1245
1273
  if (args) {
1246
1274
  d3.map(args).forEach((function(key,value) {
1247
- if (typeof this[key] === "function") {
1275
+ if (nv.utils.isFunction(this[key])) {
1248
1276
  this[key](value);
1249
1277
  }
1250
1278
  }).bind(this));
@@ -1560,6 +1588,7 @@ nv.utils.arrayEquals = function (array1, array2) {
1560
1588
  , isOrdinal = false
1561
1589
  , ticks = null
1562
1590
  , axisLabelDistance = 0
1591
+ , fontSize = undefined
1563
1592
  , duration = 250
1564
1593
  , dispatch = d3.dispatch('renderEnd')
1565
1594
  ;
@@ -1607,6 +1636,11 @@ nv.utils.arrayEquals = function (array1, array2) {
1607
1636
  .data([axisLabelText || null]);
1608
1637
  axisLabel.exit().remove();
1609
1638
 
1639
+ //only skip when fontSize is undefined so it can be cleared with a null or blank string
1640
+ if (fontSize !== undefined) {
1641
+ g.selectAll('g').select("text").style('font-size', fontSize);
1642
+ }
1643
+
1610
1644
  var xLabelMargin;
1611
1645
  var axisMaxMin;
1612
1646
  var w;
@@ -1657,6 +1691,8 @@ nv.utils.arrayEquals = function (array1, array2) {
1657
1691
  var xTicks = g.selectAll('g').select("text");
1658
1692
  var rotateLabelsRule = '';
1659
1693
  if (rotateLabels%360) {
1694
+ //Reset transform on ticks so textHeight can be calculated correctly
1695
+ xTicks.attr('transform', '');
1660
1696
  //Calculate the longest xTick width
1661
1697
  xTicks.each(function(d,i){
1662
1698
  var box = this.getBoundingClientRect();
@@ -1896,6 +1932,7 @@ nv.utils.arrayEquals = function (array1, array2) {
1896
1932
  height: {get: function(){return height;}, set: function(_){height=_;}},
1897
1933
  ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}},
1898
1934
  width: {get: function(){return width;}, set: function(_){width=_;}},
1935
+ fontSize: {get: function(){return fontSize;}, set: function(_){fontSize=_;}},
1899
1936
 
1900
1937
  // options that require extra logic in the setter
1901
1938
  margin: {get: function(){return margin;}, set: function(_){
@@ -1929,30 +1966,36 @@ nv.models.boxPlot = function() {
1929
1966
  // Public Variables with Default Settings
1930
1967
  //------------------------------------------------------------
1931
1968
 
1932
- var margin = {top: 0, right: 0, bottom: 0, left: 0}
1933
- , width = 960
1934
- , height = 500
1935
- , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
1936
- , x = d3.scale.ordinal()
1937
- , y = d3.scale.linear()
1938
- , getX = function(d) { return d.x }
1939
- , getY = function(d) { return d.y }
1940
- , color = nv.utils.defaultColor()
1941
- , container = null
1942
- , xDomain
1943
- , yDomain
1944
- , xRange
1945
- , yRange
1946
- , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
1947
- , duration = 250
1948
- , maxBoxWidth = null
1949
- ;
1969
+ var margin = {top: 0, right: 0, bottom: 0, left: 0},
1970
+ width = 960,
1971
+ height = 500,
1972
+ id = Math.floor(Math.random() * 10000), // Create semi-unique ID in case user doesn't select one
1973
+ xScale = d3.scale.ordinal(),
1974
+ yScale = d3.scale.linear(),
1975
+ getX = function(d) { return d.label }, // Default data model selectors.
1976
+ getQ1 = function(d) { return d.values.Q1 },
1977
+ getQ2 = function(d) { return d.values.Q2 },
1978
+ getQ3 = function(d) { return d.values.Q3 },
1979
+ getWl = function(d) { return d.values.whisker_low },
1980
+ getWh = function(d) { return d.values.whisker_high },
1981
+ getColor = function(d) { return d.color },
1982
+ getOlItems = function(d) { return d.values.outliers },
1983
+ getOlValue = function(d, i, j) { return d },
1984
+ getOlLabel = function(d, i, j) { return d },
1985
+ getOlColor = function(d, i, j) { return undefined },
1986
+ color = nv.utils.defaultColor(),
1987
+ container = null,
1988
+ xDomain, xRange,
1989
+ yDomain, yRange,
1990
+ dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd'),
1991
+ duration = 250,
1992
+ maxBoxWidth = null;
1950
1993
 
1951
1994
  //============================================================
1952
1995
  // Private Variables
1953
1996
  //------------------------------------------------------------
1954
1997
 
1955
- var x0, y0;
1998
+ var xScale0, yScale0;
1956
1999
  var renderWatch = nv.utils.renderWatch(dispatch, duration);
1957
2000
 
1958
2001
  function chart(selection) {
@@ -1965,45 +2008,38 @@ nv.models.boxPlot = function() {
1965
2008
  nv.utils.initSVG(container);
1966
2009
 
1967
2010
  // Setup Scales
1968
- x .domain(xDomain || data.map(function(d,i) { return getX(d,i); }))
1969
- .rangeBands(xRange || [0, availableWidth], .1);
2011
+ xScale.domain(xDomain || data.map(function(d,i) { return getX(d,i); }))
2012
+ .rangeBands(xRange || [0, availableWidth], 0.1);
1970
2013
 
1971
2014
  // if we know yDomain, no need to calculate
1972
2015
  var yData = []
1973
2016
  if (!yDomain) {
1974
2017
  // (y-range is based on quartiles, whiskers and outliers)
1975
-
1976
- // lower values
1977
- var yMin = d3.min(data.map(function(d) {
1978
- var min_arr = [];
1979
-
1980
- min_arr.push(d.values.Q1);
1981
- if (d.values.hasOwnProperty('whisker_low') && d.values.whisker_low !== null) { min_arr.push(d.values.whisker_low); }
1982
- if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { min_arr = min_arr.concat(d.values.outliers); }
1983
-
1984
- return d3.min(min_arr);
1985
- }));
1986
-
1987
- // upper values
1988
- var yMax = d3.max(data.map(function(d) {
1989
- var max_arr = [];
1990
-
1991
- max_arr.push(d.values.Q3);
1992
- if (d.values.hasOwnProperty('whisker_high') && d.values.whisker_high !== null) { max_arr.push(d.values.whisker_high); }
1993
- if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { max_arr = max_arr.concat(d.values.outliers); }
1994
-
1995
- return d3.max(max_arr);
1996
- }));
1997
-
2018
+ var values = [], yMin, yMax;
2019
+ data.forEach(function (d, i) {
2020
+ var q1 = getQ1(d), q3 = getQ3(d), wl = getWl(d), wh = getWh(d);
2021
+ var olItems = getOlItems(d);
2022
+ if (olItems) {
2023
+ olItems.forEach(function (e, i) {
2024
+ values.push(getOlValue(e, i, undefined));
2025
+ });
2026
+ }
2027
+ if (wl) { values.push(wl) }
2028
+ if (q1) { values.push(q1) }
2029
+ if (q3) { values.push(q3) }
2030
+ if (wh) { values.push(wh) }
2031
+ });
2032
+ yMin = d3.min(values);
2033
+ yMax = d3.max(values);
1998
2034
  yData = [ yMin, yMax ] ;
1999
2035
  }
2000
2036
 
2001
- y.domain(yDomain || yData);
2002
- y.range(yRange || [availableHeight, 0]);
2037
+ yScale.domain(yDomain || yData);
2038
+ yScale.range(yRange || [availableHeight, 0]);
2003
2039
 
2004
2040
  //store old scales if they exist
2005
- x0 = x0 || x;
2006
- y0 = y0 || y.copy().range([y(0),y(0)]);
2041
+ xScale0 = xScale0 || xScale;
2042
+ yScale0 = yScale0 || yScale.copy().range([yScale(0),yScale(0)]);
2007
2043
 
2008
2044
  // Setup containers and skeleton of chart
2009
2045
  var wrap = container.selectAll('g.nv-wrap').data([data]);
@@ -2014,15 +2050,15 @@ nv.models.boxPlot = function() {
2014
2050
  var boxEnter = boxplots.enter().append('g').style('stroke-opacity', 1e-6).style('fill-opacity', 1e-6);
2015
2051
  boxplots
2016
2052
  .attr('class', 'nv-boxplot')
2017
- .attr('transform', function(d,i,j) { return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)'; })
2053
+ .attr('transform', function(d,i,j) { return 'translate(' + (xScale(getX(d,i)) + xScale.rangeBand() * 0.05) + ', 0)'; })
2018
2054
  .classed('hover', function(d) { return d.hover });
2019
2055
  boxplots
2020
2056
  .watchTransition(renderWatch, 'nv-boxplot: boxplots')
2021
2057
  .style('stroke-opacity', 1)
2022
- .style('fill-opacity', .75)
2058
+ .style('fill-opacity', 0.75)
2023
2059
  .delay(function(d,i) { return i * duration / data.length })
2024
2060
  .attr('transform', function(d,i) {
2025
- return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)';
2061
+ return 'translate(' + (xScale(getX(d,i)) + xScale.rangeBand() * 0.05) + ', 0)';
2026
2062
  });
2027
2063
  boxplots.exit().remove();
2028
2064
 
@@ -2030,97 +2066,62 @@ nv.models.boxPlot = function() {
2030
2066
 
2031
2067
  // conditionally append whisker lines
2032
2068
  boxEnter.each(function(d,i) {
2033
- var box = d3.select(this);
2034
-
2035
- ['low', 'high'].forEach(function(key) {
2036
- if (d.values.hasOwnProperty('whisker_' + key) && d.values['whisker_' + key] !== null) {
2037
- box.append('line')
2038
- .style('stroke', (d.color) ? d.color : color(d,i))
2039
- .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key);
2040
-
2041
- box.append('line')
2042
- .style('stroke', (d.color) ? d.color : color(d,i))
2043
- .attr('class', 'nv-boxplot-tick nv-boxplot-' + key);
2044
- }
2045
- });
2046
- });
2047
-
2048
- // outliers
2049
- // TODO: support custom colors here
2050
- var outliers = boxplots.selectAll('.nv-boxplot-outlier').data(function(d) {
2051
- if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { return d.values.outliers; }
2052
- else { return []; }
2053
- });
2054
- outliers.enter().append('circle')
2055
- .style('fill', function(d,i,j) { return color(d,j) }).style('stroke', function(d,i,j) { return color(d,j) })
2056
- .on('mouseover', function(d,i,j) {
2057
- d3.select(this).classed('hover', true);
2058
- dispatch.elementMouseover({
2059
- series: { key: d, color: color(d,j) },
2060
- e: d3.event
2061
- });
2062
- })
2063
- .on('mouseout', function(d,i,j) {
2064
- d3.select(this).classed('hover', false);
2065
- dispatch.elementMouseout({
2066
- series: { key: d, color: color(d,j) },
2067
- e: d3.event
2068
- });
2069
- })
2070
- .on('mousemove', function(d,i) {
2071
- dispatch.elementMousemove({e: d3.event});
2069
+ var box = d3.select(this);
2070
+ [getWl, getWh].forEach(function (f) {
2071
+ if (f(d)) {
2072
+ var key = (f === getWl) ? 'low' : 'high';
2073
+ box.append('line')
2074
+ .style('stroke', getColor(d) || color(d,i))
2075
+ .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key);
2076
+ box.append('line')
2077
+ .style('stroke', getColor(d) || color(d,i))
2078
+ .attr('class', 'nv-boxplot-tick nv-boxplot-' + key);
2079
+ }
2072
2080
  });
2081
+ });
2073
2082
 
2074
- outliers.attr('class', 'nv-boxplot-outlier');
2075
- outliers
2076
- .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier')
2077
- .attr('cx', x.rangeBand() * .45)
2078
- .attr('cy', function(d,i,j) { return y(d); })
2079
- .attr('r', '3');
2080
- outliers.exit().remove();
2081
-
2082
- var box_width = function() { return (maxBoxWidth === null ? x.rangeBand() * .9 : Math.min(75, x.rangeBand() * .9)); };
2083
- var box_left = function() { return x.rangeBand() * .45 - box_width()/2; };
2084
- var box_right = function() { return x.rangeBand() * .45 + box_width()/2; };
2083
+ var box_width = function() { return (maxBoxWidth === null ? xScale.rangeBand() * 0.9 : Math.min(75, xScale.rangeBand() * 0.9)); };
2084
+ var box_left = function() { return xScale.rangeBand() * 0.45 - box_width()/2; };
2085
+ var box_right = function() { return xScale.rangeBand() * 0.45 + box_width()/2; };
2085
2086
 
2086
2087
  // update whisker lines and ticks
2087
- ['low', 'high'].forEach(function(key) {
2088
- var endpoint = (key === 'low') ? 'Q1' : 'Q3';
2089
-
2090
- boxplots.select('line.nv-boxplot-whisker.nv-boxplot-' + key)
2091
- .watchTransition(renderWatch, 'nv-boxplot: boxplots')
2092
- .attr('x1', x.rangeBand() * .45 )
2093
- .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); })
2094
- .attr('x2', x.rangeBand() * .45 )
2095
- .attr('y2', function(d,i) { return y(d.values[endpoint]); });
2096
-
2097
- boxplots.select('line.nv-boxplot-tick.nv-boxplot-' + key)
2098
- .watchTransition(renderWatch, 'nv-boxplot: boxplots')
2099
- .attr('x1', box_left )
2100
- .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); })
2101
- .attr('x2', box_right )
2102
- .attr('y2', function(d,i) { return y(d.values['whisker_' + key]); });
2088
+ [getWl, getWh].forEach(function (f) {
2089
+ var key = (f === getWl) ? 'low' : 'high';
2090
+ var endpoint = (f === getWl) ? getQ1 : getQ3;
2091
+ boxplots.select('line.nv-boxplot-whisker.nv-boxplot-' + key)
2092
+ .watchTransition(renderWatch, 'nv-boxplot: boxplots')
2093
+ .attr('x1', xScale.rangeBand() * 0.45 )
2094
+ .attr('y1', function(d,i) { return yScale(f(d)); })
2095
+ .attr('x2', xScale.rangeBand() * 0.45 )
2096
+ .attr('y2', function(d,i) { return yScale(endpoint(d)); });
2097
+ boxplots.select('line.nv-boxplot-tick.nv-boxplot-' + key)
2098
+ .watchTransition(renderWatch, 'nv-boxplot: boxplots')
2099
+ .attr('x1', box_left )
2100
+ .attr('y1', function(d,i) { return yScale(f(d)); })
2101
+ .attr('x2', box_right )
2102
+ .attr('y2', function(d,i) { return yScale(f(d)); });
2103
2103
  });
2104
2104
 
2105
- ['low', 'high'].forEach(function(key) {
2106
- boxEnter.selectAll('.nv-boxplot-' + key)
2107
- .on('mouseover', function(d,i,j) {
2108
- d3.select(this).classed('hover', true);
2109
- dispatch.elementMouseover({
2110
- series: { key: d.values['whisker_' + key], color: color(d,j) },
2111
- e: d3.event
2112
- });
2113
- })
2114
- .on('mouseout', function(d,i,j) {
2115
- d3.select(this).classed('hover', false);
2116
- dispatch.elementMouseout({
2117
- series: { key: d.values['whisker_' + key], color: color(d,j) },
2118
- e: d3.event
2119
- });
2120
- })
2121
- .on('mousemove', function(d,i) {
2122
- dispatch.elementMousemove({e: d3.event});
2123
- });
2105
+ [getWl, getWh].forEach(function (f) {
2106
+ var key = (f === getWl) ? 'low' : 'high';
2107
+ boxEnter.selectAll('.nv-boxplot-' + key)
2108
+ .on('mouseover', function(d,i,j) {
2109
+ d3.select(this).classed('hover', true);
2110
+ dispatch.elementMouseover({
2111
+ series: { key: f(d), color: getColor(d) || color(d,j) },
2112
+ e: d3.event
2113
+ });
2114
+ })
2115
+ .on('mouseout', function(d,i,j) {
2116
+ d3.select(this).classed('hover', false);
2117
+ dispatch.elementMouseout({
2118
+ series: { key: f(d), color: getColor(d) || color(d,j) },
2119
+ e: d3.event
2120
+ });
2121
+ })
2122
+ .on('mousemove', function(d,i) {
2123
+ dispatch.elementMousemove({e: d3.event});
2124
+ });
2124
2125
  });
2125
2126
 
2126
2127
  // boxes
@@ -2130,12 +2131,12 @@ nv.models.boxPlot = function() {
2130
2131
  .on('mouseover', function(d,i) {
2131
2132
  d3.select(this).classed('hover', true);
2132
2133
  dispatch.elementMouseover({
2133
- key: d.label,
2134
- value: d.label,
2134
+ key: getX(d),
2135
+ value: getX(d),
2135
2136
  series: [
2136
- { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) },
2137
- { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) },
2138
- { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) }
2137
+ { key: 'Q3', value: getQ3(d), color: getColor(d) || color(d,i) },
2138
+ { key: 'Q2', value: getQ2(d), color: getColor(d) || color(d,i) },
2139
+ { key: 'Q1', value: getQ1(d), color: getColor(d) || color(d,i) }
2139
2140
  ],
2140
2141
  data: d,
2141
2142
  index: i,
@@ -2145,12 +2146,12 @@ nv.models.boxPlot = function() {
2145
2146
  .on('mouseout', function(d,i) {
2146
2147
  d3.select(this).classed('hover', false);
2147
2148
  dispatch.elementMouseout({
2148
- key: d.label,
2149
- value: d.label,
2149
+ key: getX(d),
2150
+ value: getX(d),
2150
2151
  series: [
2151
- { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) },
2152
- { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) },
2153
- { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) }
2152
+ { key: 'Q3', value: getQ3(d), color: getColor(d) || color(d,i) },
2153
+ { key: 'Q2', value: getQ2(d), color: getColor(d) || color(d,i) },
2154
+ { key: 'Q1', value: getQ1(d), color: getColor(d) || color(d,i) }
2154
2155
  ],
2155
2156
  data: d,
2156
2157
  index: i,
@@ -2164,13 +2165,12 @@ nv.models.boxPlot = function() {
2164
2165
  // box transitions
2165
2166
  boxplots.select('rect.nv-boxplot-box')
2166
2167
  .watchTransition(renderWatch, 'nv-boxplot: boxes')
2167
- .attr('y', function(d,i) { return y(d.values.Q3); })
2168
+ .attr('y', function(d,i) { return yScale(getQ3(d)); })
2168
2169
  .attr('width', box_width)
2169
2170
  .attr('x', box_left )
2170
-
2171
- .attr('height', function(d,i) { return Math.abs(y(d.values.Q3) - y(d.values.Q1)) || 1 })
2172
- .style('fill', function(d,i) { return d.color || color(d,i) })
2173
- .style('stroke', function(d,i) { return d.color || color(d,i) });
2171
+ .attr('height', function(d,i) { return Math.abs(yScale(getQ3(d)) - yScale(getQ1(d))) || 1 })
2172
+ .style('fill', function(d,i) { return getColor(d) || color(d,i) })
2173
+ .style('stroke', function(d,i) { return getColor(d) || color(d,i) });
2174
2174
 
2175
2175
  // median line
2176
2176
  boxEnter.append('line').attr('class', 'nv-boxplot-median');
@@ -2178,13 +2178,46 @@ nv.models.boxPlot = function() {
2178
2178
  boxplots.select('line.nv-boxplot-median')
2179
2179
  .watchTransition(renderWatch, 'nv-boxplot: boxplots line')
2180
2180
  .attr('x1', box_left)
2181
- .attr('y1', function(d,i) { return y(d.values.Q2); })
2181
+ .attr('y1', function(d,i) { return yScale(getQ2(d)); })
2182
2182
  .attr('x2', box_right)
2183
- .attr('y2', function(d,i) { return y(d.values.Q2); });
2183
+ .attr('y2', function(d,i) { return yScale(getQ2(d)); });
2184
+
2185
+ // outliers
2186
+ var outliers = boxplots.selectAll('.nv-boxplot-outlier').data(function(d) {
2187
+ return getOlItems(d) || [];
2188
+ });
2189
+ outliers.enter().append('circle')
2190
+ .style('fill', function(d,i,j) { return getOlColor(d,i,j) || color(d,j) })
2191
+ .style('stroke', function(d,i,j) { return getOlColor(d,i,j) || color(d,j) })
2192
+ .style('z-index', 9000)
2193
+ .on('mouseover', function(d,i,j) {
2194
+ d3.select(this).classed('hover', true);
2195
+ dispatch.elementMouseover({
2196
+ series: { key: getOlLabel(d,i,j), color: getOlColor(d,i,j) || color(d,j) },
2197
+ e: d3.event
2198
+ });
2199
+ })
2200
+ .on('mouseout', function(d,i,j) {
2201
+ d3.select(this).classed('hover', false);
2202
+ dispatch.elementMouseout({
2203
+ series: { key: getOlLabel(d,i,j), color: getOlColor(d,i,j) || color(d,j) },
2204
+ e: d3.event
2205
+ });
2206
+ })
2207
+ .on('mousemove', function(d,i) {
2208
+ dispatch.elementMousemove({e: d3.event});
2209
+ });
2210
+ outliers.attr('class', 'nv-boxplot-outlier');
2211
+ outliers
2212
+ .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier')
2213
+ .attr('cx', xScale.rangeBand() * 0.45)
2214
+ .attr('cy', function(d,i,j) { return yScale(getOlValue(d,i,j)); })
2215
+ .attr('r', '3');
2216
+ outliers.exit().remove();
2184
2217
 
2185
2218
  //store old scales for use in transitions on update
2186
- x0 = x.copy();
2187
- y0 = y.copy();
2219
+ xScale0 = xScale.copy();
2220
+ yScale0 = yScale.copy();
2188
2221
  });
2189
2222
 
2190
2223
  renderWatch.renderEnd('nv-boxplot immediate');
@@ -2200,20 +2233,37 @@ nv.models.boxPlot = function() {
2200
2233
 
2201
2234
  chart._options = Object.create({}, {
2202
2235
  // simple options, just get/set the necessary values
2203
- width: {get: function(){return width;}, set: function(_){width=_;}},
2204
- height: {get: function(){return height;}, set: function(_){height=_;}},
2236
+ width: {get: function(){return width;}, set: function(_){width=_;}},
2237
+ height: {get: function(){return height;}, set: function(_){height=_;}},
2205
2238
  maxBoxWidth: {get: function(){return maxBoxWidth;}, set: function(_){maxBoxWidth=_;}},
2206
- x: {get: function(){return getX;}, set: function(_){getX=_;}},
2207
- y: {get: function(){return getY;}, set: function(_){getY=_;}},
2208
- xScale: {get: function(){return x;}, set: function(_){x=_;}},
2209
- yScale: {get: function(){return y;}, set: function(_){y=_;}},
2239
+ x: {get: function(){return getX;}, set: function(_){getX=_;}},
2240
+ q1: {get: function(){return getQ1;}, set: function(_){getQ1=_;}},
2241
+ q2: {get: function(){return getQ2;}, set: function(_){getQ2=_;}},
2242
+ q3: {get: function(){return getQ3;}, set: function(_){getQ3=_;}},
2243
+ wl: {get: function(){return getWl;}, set: function(_){getWl=_;}},
2244
+ wh: {get: function(){return getWh;}, set: function(_){getWh=_;}},
2245
+ itemColor: {get: function(){return getColor;}, set: function(_){getColor=_;}},
2246
+ outliers: {get: function(){return getOlItems;}, set: function(_){getOlItems=_;}},
2247
+ outlierValue: {get: function(){return getOlValue;}, set: function(_){getOlValue=_;}},
2248
+ outlierLabel: {get: function(){return getOlLabel;}, set: function(_){getOlLabel=_;}},
2249
+ outlierColor: {get: function(){return getOlColor;}, set: function(_){getOlColor=_;}},
2250
+ xScale: {get: function(){return xScale;}, set: function(_){xScale=_;}},
2251
+ yScale: {get: function(){return yScale;}, set: function(_){yScale=_;}},
2210
2252
  xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
2211
2253
  yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
2212
2254
  xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
2213
2255
  yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
2214
2256
  id: {get: function(){return id;}, set: function(_){id=_;}},
2215
2257
  // rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
2216
-
2258
+ y: {
2259
+ get: function() {
2260
+ console.warn('BoxPlot \'y\' chart option is deprecated. Please use model overrides instead.');
2261
+ return {};
2262
+ },
2263
+ set: function(_) {
2264
+ console.warn('BoxPlot \'y\' chart option is deprecated. Please use model overrides instead.');
2265
+ }
2266
+ },
2217
2267
  // options that require extra logic in the setter
2218
2268
  margin: {get: function(){return margin;}, set: function(_){
2219
2269
  margin.top = _.top !== undefined ? _.top : margin.top;
@@ -2241,26 +2291,23 @@ nv.models.boxPlotChart = function() {
2241
2291
  // Public Variables with Default Settings
2242
2292
  //------------------------------------------------------------
2243
2293
 
2244
- var boxplot = nv.models.boxPlot()
2245
- , xAxis = nv.models.axis()
2246
- , yAxis = nv.models.axis()
2247
- ;
2294
+ var boxplot = nv.models.boxPlot(),
2295
+ xAxis = nv.models.axis(),
2296
+ yAxis = nv.models.axis();
2248
2297
 
2249
- var margin = {top: 15, right: 10, bottom: 50, left: 60}
2250
- , width = null
2251
- , height = null
2252
- , color = nv.utils.getColor()
2253
- , showXAxis = true
2254
- , showYAxis = true
2255
- , rightAlignYAxis = false
2256
- , staggerLabels = false
2257
- , tooltip = nv.models.tooltip()
2258
- , x
2259
- , y
2260
- , noData = "No Data Available."
2261
- , dispatch = d3.dispatch('beforeUpdate', 'renderEnd')
2262
- , duration = 250
2263
- ;
2298
+ var margin = {top: 15, right: 10, bottom: 50, left: 60},
2299
+ width = null,
2300
+ height = null,
2301
+ color = nv.utils.getColor(),
2302
+ showXAxis = true,
2303
+ showYAxis = true,
2304
+ rightAlignYAxis = false,
2305
+ staggerLabels = false,
2306
+ tooltip = nv.models.tooltip(),
2307
+ x, y,
2308
+ noData = 'No Data Available.',
2309
+ dispatch = d3.dispatch('beforeUpdate', 'renderEnd'),
2310
+ duration = 250;
2264
2311
 
2265
2312
  xAxis
2266
2313
  .orient('bottom')
@@ -2287,13 +2334,10 @@ nv.models.boxPlotChart = function() {
2287
2334
  if (showYAxis) renderWatch.models(yAxis);
2288
2335
 
2289
2336
  selection.each(function(data) {
2290
- var container = d3.select(this),
2291
- that = this;
2337
+ var container = d3.select(this), that = this;
2292
2338
  nv.utils.initSVG(container);
2293
- var availableWidth = (width || parseInt(container.style('width')) || 960)
2294
- - margin.left - margin.right,
2295
- availableHeight = (height || parseInt(container.style('height')) || 400)
2296
- - margin.top - margin.bottom;
2339
+ var availableWidth = (width || parseInt(container.style('width')) || 960) - margin.left - margin.right;
2340
+ var availableHeight = (height || parseInt(container.style('height')) || 400) - margin.top - margin.bottom;
2297
2341
 
2298
2342
  chart.update = function() {
2299
2343
  dispatch.beforeUpdate();
@@ -2301,9 +2345,9 @@ nv.models.boxPlotChart = function() {
2301
2345
  };
2302
2346
  chart.container = this;
2303
2347
 
2304
- // Display No Data message if there's nothing to show. (quartiles required at minimum)
2305
- if (!data || !data.length ||
2306
- !data.filter(function(d) { return d.values.hasOwnProperty("Q1") && d.values.hasOwnProperty("Q2") && d.values.hasOwnProperty("Q3"); }).length) {
2348
+ // TODO still need to find a way to validate quartile data presence using boxPlot callbacks.
2349
+ // Display No Data message if there's nothing to show. (quartiles required at minimum).
2350
+ if (!data || !data.length) {
2307
2351
  var noDataText = container.selectAll('.nv-noData').data([noData]);
2308
2352
 
2309
2353
  noDataText.enter().append('text')
@@ -2337,25 +2381,21 @@ nv.models.boxPlotChart = function() {
2337
2381
  .append('line');
2338
2382
 
2339
2383
  gEnter.append('g').attr('class', 'nv-barsWrap');
2340
-
2341
2384
  g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2342
2385
 
2343
2386
  if (rightAlignYAxis) {
2344
- g.select(".nv-y.nv-axis")
2345
- .attr("transform", "translate(" + availableWidth + ",0)");
2387
+ g.select('.nv-y.nv-axis')
2388
+ .attr('transform', 'translate(' + availableWidth + ',0)');
2346
2389
  }
2347
2390
 
2348
2391
  // Main Chart Component(s)
2349
- boxplot
2350
- .width(availableWidth)
2351
- .height(availableHeight);
2392
+ boxplot.width(availableWidth).height(availableHeight);
2352
2393
 
2353
2394
  var barsWrap = g.select('.nv-barsWrap')
2354
2395
  .datum(data.filter(function(d) { return !d.disabled }))
2355
2396
 
2356
2397
  barsWrap.transition().call(boxplot);
2357
2398
 
2358
-
2359
2399
  defsEnter.append('clipPath')
2360
2400
  .attr('id', 'nv-x-label-clip-' + boxplot.id())
2361
2401
  .append('rect');
@@ -2379,7 +2419,7 @@ nv.models.boxPlotChart = function() {
2379
2419
  if (staggerLabels) {
2380
2420
  xTicks
2381
2421
  .selectAll('text')
2382
- .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
2422
+ .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 === 0 ? '5' : '17') + ')' })
2383
2423
  }
2384
2424
  }
2385
2425
 
@@ -2393,11 +2433,11 @@ nv.models.boxPlotChart = function() {
2393
2433
  }
2394
2434
 
2395
2435
  // Zero line
2396
- g.select(".nv-zeroLine line")
2397
- .attr("x1",0)
2398
- .attr("x2",availableWidth)
2399
- .attr("y1", y(0))
2400
- .attr("y2", y(0))
2436
+ g.select('.nv-zeroLine line')
2437
+ .attr('x1',0)
2438
+ .attr('x2',availableWidth)
2439
+ .attr('y1', y(0))
2440
+ .attr('y2', y(0))
2401
2441
  ;
2402
2442
 
2403
2443
  //============================================================
@@ -2504,8 +2544,19 @@ nv.models.bullet = function() {
2504
2544
  , tickFormat = null
2505
2545
  , color = nv.utils.getColor(['#1f77b4'])
2506
2546
  , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove')
2547
+ , defaultRangeLabels = ["Maximum", "Mean", "Minimum"]
2548
+ , legacyRangeClassNames = ["Max", "Avg", "Min"]
2507
2549
  ;
2508
2550
 
2551
+ function sortLabels(labels, values){
2552
+ var lz = labels.slice();
2553
+ labels.sort(function(a, b){
2554
+ var iA = lz.indexOf(a);
2555
+ var iB = lz.indexOf(b);
2556
+ return d3.descending(values[iA], values[iB]);
2557
+ });
2558
+ };
2559
+
2509
2560
  function chart(selection) {
2510
2561
  selection.each(function(d, i) {
2511
2562
  var availableWidth = width - margin.left - margin.right,
@@ -2514,13 +2565,23 @@ nv.models.bullet = function() {
2514
2565
  container = d3.select(this);
2515
2566
  nv.utils.initSVG(container);
2516
2567
 
2517
- var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
2518
- markerz = markers.call(this, d, i).slice().sort(d3.descending),
2519
- measurez = measures.call(this, d, i).slice().sort(d3.descending),
2568
+ var rangez = ranges.call(this, d, i).slice(),
2569
+ markerz = markers.call(this, d, i).slice(),
2570
+ measurez = measures.call(this, d, i).slice(),
2520
2571
  rangeLabelz = rangeLabels.call(this, d, i).slice(),
2521
2572
  markerLabelz = markerLabels.call(this, d, i).slice(),
2522
2573
  measureLabelz = measureLabels.call(this, d, i).slice();
2523
2574
 
2575
+ // Sort labels according to their sorted values
2576
+ sortLabels(rangeLabelz, rangez);
2577
+ sortLabels(markerLabelz, markerz);
2578
+ sortLabels(measureLabelz, measurez);
2579
+
2580
+ // sort values descending
2581
+ rangez.sort(d3.descending);
2582
+ markerz.sort(d3.descending);
2583
+ measurez.sort(d3.descending);
2584
+
2524
2585
  // Setup Scales
2525
2586
  // Compute the new x-scale.
2526
2587
  var x1 = d3.scale.linear()
@@ -2545,9 +2606,14 @@ nv.models.bullet = function() {
2545
2606
  var gEnter = wrapEnter.append('g');
2546
2607
  var g = wrap.select('g');
2547
2608
 
2548
- gEnter.append('rect').attr('class', 'nv-range nv-rangeMax');
2549
- gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg');
2550
- gEnter.append('rect').attr('class', 'nv-range nv-rangeMin');
2609
+ for(var i=0,il=rangez.length; i<il; i++){
2610
+ var rangeClassNames = 'nv-range nv-range'+i;
2611
+ if(i <= 2){
2612
+ rangeClassNames = rangeClassNames + ' nv-range'+legacyRangeClassNames[i];
2613
+ }
2614
+ gEnter.append('rect').attr('class', rangeClassNames);
2615
+ }
2616
+
2551
2617
  gEnter.append('rect').attr('class', 'nv-measure');
2552
2618
 
2553
2619
  wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
@@ -2557,25 +2623,14 @@ nv.models.bullet = function() {
2557
2623
  var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
2558
2624
  xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
2559
2625
 
2560
- g.select('rect.nv-rangeMax')
2561
- .attr('height', availableHeight)
2562
- .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin))
2563
- .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin))
2564
- .datum(rangeMax > 0 ? rangeMax : rangeMin)
2565
-
2566
- g.select('rect.nv-rangeAvg')
2567
- .attr('height', availableHeight)
2568
- .attr('width', w1(rangeAvg))
2569
- .attr('x', xp1(rangeAvg))
2570
- .datum(rangeAvg)
2571
-
2572
- g.select('rect.nv-rangeMin')
2573
- .attr('height', availableHeight)
2574
- .attr('width', w1(rangeMax))
2575
- .attr('x', xp1(rangeMax))
2576
- .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax))
2577
- .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax))
2578
- .datum(rangeMax > 0 ? rangeMin : rangeMax)
2626
+ for(var i=0,il=rangez.length; i<il; i++){
2627
+ var range = rangez[i];
2628
+ g.select('rect.nv-range'+i)
2629
+ .attr('height', availableHeight)
2630
+ .attr('width', w1(range))
2631
+ .attr('x', xp1(range))
2632
+ .datum(range)
2633
+ }
2579
2634
 
2580
2635
  g.select('rect.nv-measure')
2581
2636
  .style('fill', color)
@@ -2649,7 +2704,7 @@ nv.models.bullet = function() {
2649
2704
 
2650
2705
  wrap.selectAll('.nv-range')
2651
2706
  .on('mouseover', function(d,i) {
2652
- var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
2707
+ var label = rangeLabelz[i] || defaultRangeLabels[i];
2653
2708
  dispatch.elementMouseover({
2654
2709
  value: d,
2655
2710
  label: label,
@@ -2664,7 +2719,7 @@ nv.models.bullet = function() {
2664
2719
  })
2665
2720
  })
2666
2721
  .on('mouseout', function(d,i) {
2667
- var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
2722
+ var label = rangeLabelz[i] || defaultRangeLabels[i];
2668
2723
  dispatch.elementMouseout({
2669
2724
  value: d,
2670
2725
  label: label,
@@ -3365,7 +3420,9 @@ nv.models.cumulativeLineChart = function() {
3365
3420
  gEnter.append('g').attr('class', 'nv-controlsWrap');
3366
3421
 
3367
3422
  // Legend
3368
- if (showLegend) {
3423
+ if (!showLegend) {
3424
+ g.select('.nv-legendWrap').selectAll('*').remove();
3425
+ } else {
3369
3426
  legend.width(availableWidth);
3370
3427
 
3371
3428
  g.select('.nv-legendWrap')
@@ -3382,7 +3439,9 @@ nv.models.cumulativeLineChart = function() {
3382
3439
  }
3383
3440
 
3384
3441
  // Controls
3385
- if (showControls) {
3442
+ if (!showControls) {
3443
+ g.select('.nv-controlsWrap').selectAll('*').remove();
3444
+ } else {
3386
3445
  var controlsData = [
3387
3446
  { key: 'Re-scale y-axis', disabled: !rescaleY }
3388
3447
  ];
@@ -4158,8 +4217,11 @@ nv.models.discreteBarChart = function() {
4158
4217
  gEnter.append('g').attr('class', 'nv-legendWrap');
4159
4218
 
4160
4219
  g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4161
-
4162
- if (showLegend) {
4220
+
4221
+ // Legend
4222
+ if (!showLegend) {
4223
+ g.select('.nv-legendWrap').selectAll('*').remove();
4224
+ } else {
4163
4225
  legend.width(availableWidth);
4164
4226
 
4165
4227
  g.select('.nv-legendWrap')
@@ -4180,11 +4242,6 @@ nv.models.discreteBarChart = function() {
4180
4242
  .attr("transform", "translate(" + availableWidth + ",0)");
4181
4243
  }
4182
4244
 
4183
- if (rightAlignYAxis) {
4184
- g.select(".nv-y.nv-axis")
4185
- .attr("transform", "translate(" + availableWidth + ",0)");
4186
- }
4187
-
4188
4245
  // Main Chart Component(s)
4189
4246
  discretebar
4190
4247
  .width(availableWidth)
@@ -4494,6 +4551,195 @@ nv.models.distribution = function() {
4494
4551
 
4495
4552
  return chart;
4496
4553
  }
4554
+ nv.models.forceDirectedGraph = function() {
4555
+ "use strict";
4556
+
4557
+ //============================================================
4558
+ // Public Variables with Default Settings
4559
+ //------------------------------------------------------------
4560
+ var margin = {top: 2, right: 0, bottom: 2, left: 0}
4561
+ , width = 400
4562
+ , height = 32
4563
+ , container = null
4564
+ , dispatch = d3.dispatch('renderEnd')
4565
+ , color = nv.utils.getColor(['#000'])
4566
+ , tooltip = nv.models.tooltip()
4567
+ , noData = null
4568
+ // Force directed graph specific parameters [default values]
4569
+ , linkStrength = 0.1
4570
+ , friction = 0.9
4571
+ , linkDist = 30
4572
+ , charge = -120
4573
+ , gravity = 0.1
4574
+ , theta = 0.8
4575
+ , alpha = 0.1
4576
+ , radius = 5
4577
+ // These functions allow to add extra attributes to ndes and links
4578
+ ,nodeExtras = function(nodes) { /* Do nothing */ }
4579
+ ,linkExtras = function(links) { /* Do nothing */ }
4580
+ ;
4581
+
4582
+
4583
+ //============================================================
4584
+ // Private Variables
4585
+ //------------------------------------------------------------
4586
+
4587
+ var renderWatch = nv.utils.renderWatch(dispatch);
4588
+
4589
+ function chart(selection) {
4590
+ renderWatch.reset();
4591
+
4592
+ selection.each(function(data) {
4593
+ container = d3.select(this);
4594
+ nv.utils.initSVG(container);
4595
+
4596
+ var availableWidth = nv.utils.availableWidth(width, container, margin),
4597
+ availableHeight = nv.utils.availableHeight(height, container, margin);
4598
+
4599
+ container
4600
+ .attr("width", availableWidth)
4601
+ .attr("height", availableHeight);
4602
+
4603
+ // Display No Data message if there's nothing to show.
4604
+ if (!data || !data.links || !data.nodes) {
4605
+ nv.utils.noData(chart, container)
4606
+ return chart;
4607
+ } else {
4608
+ container.selectAll('.nv-noData').remove();
4609
+ }
4610
+ container.selectAll('*').remove();
4611
+
4612
+ // Collect names of all fields in the nodes
4613
+ var nodeFieldSet = new Set();
4614
+ data.nodes.forEach(function(node) {
4615
+ var keys = Object.keys(node);
4616
+ keys.forEach(function(key) {
4617
+ nodeFieldSet.add(key);
4618
+ });
4619
+ });
4620
+
4621
+ var force = d3.layout.force()
4622
+ .nodes(data.nodes)
4623
+ .links(data.links)
4624
+ .size([availableWidth, availableHeight])
4625
+ .linkStrength(linkStrength)
4626
+ .friction(friction)
4627
+ .linkDistance(linkDist)
4628
+ .charge(charge)
4629
+ .gravity(gravity)
4630
+ .theta(theta)
4631
+ .alpha(alpha)
4632
+ .start();
4633
+
4634
+ var link = container.selectAll(".link")
4635
+ .data(data.links)
4636
+ .enter().append("line")
4637
+ .attr("class", "nv-force-link")
4638
+ .style("stroke-width", function(d) { return Math.sqrt(d.value); });
4639
+
4640
+ var node = container.selectAll(".node")
4641
+ .data(data.nodes)
4642
+ .enter()
4643
+ .append("g")
4644
+ .attr("class", "nv-force-node")
4645
+ .call(force.drag);
4646
+
4647
+ node
4648
+ .append("circle")
4649
+ .attr("r", radius)
4650
+ .style("fill", function(d) { return color(d) } )
4651
+ .on("mouseover", function(evt) {
4652
+ container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
4653
+ .attr('y1', evt.py);
4654
+ container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
4655
+ .attr('x2', evt.px);
4656
+
4657
+ // Add 'series' object to
4658
+ var nodeColor = color(evt);
4659
+ evt.series = [];
4660
+ nodeFieldSet.forEach(function(field) {
4661
+ evt.series.push({
4662
+ color: nodeColor,
4663
+ key: field,
4664
+ value: evt[field]
4665
+ });
4666
+ });
4667
+ tooltip.data(evt).hidden(false);
4668
+ })
4669
+ .on("mouseout", function(d) {
4670
+ tooltip.hidden(true);
4671
+ });
4672
+
4673
+ tooltip.headerFormatter(function(d) {return "Node";});
4674
+
4675
+ // Apply extra attributes to nodes and links (if any)
4676
+ linkExtras(link);
4677
+ nodeExtras(node);
4678
+
4679
+ force.on("tick", function() {
4680
+ link.attr("x1", function(d) { return d.source.x; })
4681
+ .attr("y1", function(d) { return d.source.y; })
4682
+ .attr("x2", function(d) { return d.target.x; })
4683
+ .attr("y2", function(d) { return d.target.y; });
4684
+
4685
+ node.attr("transform", function(d) {
4686
+ return "translate(" + d.x + ", " + d.y + ")";
4687
+ });
4688
+ });
4689
+ });
4690
+
4691
+ return chart;
4692
+ }
4693
+
4694
+ //============================================================
4695
+ // Expose Public Variables
4696
+ //------------------------------------------------------------
4697
+
4698
+ chart.options = nv.utils.optionsFunc.bind(chart);
4699
+
4700
+ chart._options = Object.create({}, {
4701
+ // simple options, just get/set the necessary values
4702
+ width: {get: function(){return width;}, set: function(_){width=_;}},
4703
+ height: {get: function(){return height;}, set: function(_){height=_;}},
4704
+
4705
+ // Force directed graph specific parameters
4706
+ linkStrength:{get: function(){return linkStrength;}, set: function(_){linkStrength=_;}},
4707
+ friction: {get: function(){return friction;}, set: function(_){friction=_;}},
4708
+ linkDist: {get: function(){return linkDist;}, set: function(_){linkDist=_;}},
4709
+ charge: {get: function(){return charge;}, set: function(_){charge=_;}},
4710
+ gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}},
4711
+ theta: {get: function(){return theta;}, set: function(_){theta=_;}},
4712
+ alpha: {get: function(){return alpha;}, set: function(_){alpha=_;}},
4713
+ radius: {get: function(){return radius;}, set: function(_){radius=_;}},
4714
+
4715
+ //functor options
4716
+ x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}},
4717
+ y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}},
4718
+
4719
+ // options that require extra logic in the setter
4720
+ margin: {get: function(){return margin;}, set: function(_){
4721
+ margin.top = _.top !== undefined ? _.top : margin.top;
4722
+ margin.right = _.right !== undefined ? _.right : margin.right;
4723
+ margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
4724
+ margin.left = _.left !== undefined ? _.left : margin.left;
4725
+ }},
4726
+ color: {get: function(){return color;}, set: function(_){
4727
+ color = nv.utils.getColor(_);
4728
+ }},
4729
+ noData: {get: function(){return noData;}, set: function(_){noData=_;}},
4730
+ nodeExtras: {get: function(){return nodeExtras;}, set: function(_){
4731
+ nodeExtras = _;
4732
+ }},
4733
+ linkExtras: {get: function(){return linkExtras;}, set: function(_){
4734
+ linkExtras = _;
4735
+ }}
4736
+ });
4737
+
4738
+ chart.dispatch = dispatch;
4739
+ chart.tooltip = tooltip;
4740
+ nv.utils.initOptions(chart);
4741
+ return chart;
4742
+ };
4497
4743
  nv.models.furiousLegend = function() {
4498
4744
  "use strict";
4499
4745
 
@@ -4675,13 +4921,13 @@ nv.models.furiousLegend = function() {
4675
4921
  var seriesWidths = [];
4676
4922
  series.each(function(d,i) {
4677
4923
  var legendText;
4678
- if (getKey(d).length > maxKeyLength) {
4924
+ if (getKey(d) && (getKey(d).length > maxKeyLength)) {
4679
4925
  var trimmedKey = getKey(d).substring(0, maxKeyLength);
4680
4926
  legendText = d3.select(this).select('text').text(trimmedKey + "...");
4681
4927
  d3.select(this).append("svg:title").text(getKey(d));
4682
4928
  } else {
4683
4929
  legendText = d3.select(this).select('text');
4684
- }
4930
+ }
4685
4931
  var nodeTextLength;
4686
4932
  try {
4687
4933
  nodeTextLength = legendText.node().getComputedTextLength();
@@ -5180,7 +5426,9 @@ nv.models.historicalBarChart = function(bar_model) {
5180
5426
  gEnter.append('g').attr('class', 'nv-interactive');
5181
5427
 
5182
5428
  // Legend
5183
- if (showLegend) {
5429
+ if (!showLegend) {
5430
+ g.select('.nv-legendWrap').selectAll('*').remove();
5431
+ } else {
5184
5432
  legend.width(availableWidth);
5185
5433
 
5186
5434
  g.select('.nv-legendWrap')
@@ -5534,7 +5782,7 @@ nv.models.legend = function() {
5534
5782
  .attr('class','nv-legend-symbol')
5535
5783
  .attr('r', 5);
5536
5784
 
5537
- seriesShape = series.select('circle');
5785
+ seriesShape = series.select('.nv-legend-symbol');
5538
5786
  } else if (vers == 'furious') {
5539
5787
  seriesEnter.append('rect')
5540
5788
  .style('stroke-width', 2)
@@ -5651,13 +5899,13 @@ nv.models.legend = function() {
5651
5899
  var seriesWidths = [];
5652
5900
  series.each(function(d,i) {
5653
5901
  var legendText;
5654
- if (getKey(d).length > maxKeyLength) {
5902
+ if (getKey(d) && (getKey(d).length > maxKeyLength)) {
5655
5903
  var trimmedKey = getKey(d).substring(0, maxKeyLength);
5656
5904
  legendText = d3.select(this).select('text').text(trimmedKey + "...");
5657
5905
  d3.select(this).append("svg:title").text(getKey(d));
5658
5906
  } else {
5659
5907
  legendText = d3.select(this).select('text');
5660
- }
5908
+ }
5661
5909
  var nodeTextLength;
5662
5910
  try {
5663
5911
  nodeTextLength = legendText.node().getComputedTextLength();
@@ -6104,6 +6352,7 @@ nv.models.lineChart = function() {
6104
6352
  , width = null
6105
6353
  , height = null
6106
6354
  , showLegend = true
6355
+ , legendPosition = 'top'
6107
6356
  , showXAxis = true
6108
6357
  , showYAxis = true
6109
6358
  , rightAlignYAxis = false
@@ -6254,20 +6503,27 @@ nv.models.lineChart = function() {
6254
6503
  contextEnter.append('g').attr('class', 'nv-x nv-brush');
6255
6504
 
6256
6505
  // Legend
6257
- if (showLegend) {
6506
+ if (!showLegend) {
6507
+ g.select('.nv-legendWrap').selectAll('*').remove();
6508
+ } else {
6258
6509
  legend.width(availableWidth);
6259
6510
 
6260
6511
  g.select('.nv-legendWrap')
6261
6512
  .datum(data)
6262
6513
  .call(legend);
6263
6514
 
6264
- if ( margin.top != legend.height()) {
6265
- margin.top = legend.height();
6266
- availableHeight1 = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focusHeight : 0);
6267
- }
6515
+ if (legendPosition === 'bottom') {
6516
+ wrap.select('.nv-legendWrap')
6517
+ .attr('transform', 'translate(0,' + (availableHeight1) +')');
6518
+ } else if (legendPosition === 'top') {
6519
+ if ( margin.top != legend.height()) {
6520
+ margin.top = legend.height();
6521
+ availableHeight1 = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focusHeight : 0);
6522
+ }
6268
6523
 
6269
- wrap.select('.nv-legendWrap')
6270
- .attr('transform', 'translate(0,' + (-margin.top) +')');
6524
+ wrap.select('.nv-legendWrap')
6525
+ .attr('transform', 'translate(0,' + (-margin.top) +')');
6526
+ }
6271
6527
  }
6272
6528
 
6273
6529
  wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
@@ -6491,11 +6747,13 @@ nv.models.lineChart = function() {
6491
6747
  allData[indexToHighlight].highlight = true;
6492
6748
  }
6493
6749
 
6750
+ var defaultValueFormatter = function(d,i) {
6751
+ return d == null ? "N/A" : yAxis.tickFormat()(d);
6752
+ };
6753
+
6494
6754
  interactiveLayer.tooltip
6495
6755
  .chartContainer(chart.container.parentNode)
6496
- .valueFormatter(function(d,i) {
6497
- return d === null ? "N/A" : yAxis.tickFormat()(d);
6498
- })
6756
+ .valueFormatter(interactiveLayer.tooltip.valueFormatter() || defaultValueFormatter)
6499
6757
  .data({
6500
6758
  value: chart.x()( singlePoint,pointIndex ),
6501
6759
  index: pointIndex,
@@ -6669,6 +6927,7 @@ nv.models.lineChart = function() {
6669
6927
  width: {get: function(){return width;}, set: function(_){width=_;}},
6670
6928
  height: {get: function(){return height;}, set: function(_){height=_;}},
6671
6929
  showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
6930
+ legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}},
6672
6931
  showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
6673
6932
  showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
6674
6933
  focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
@@ -6749,8 +7008,7 @@ nv.models.lineWithFocusChart = function() {
6749
7008
  return nv.models.lineChart()
6750
7009
  .margin({ bottom: 30 })
6751
7010
  .focusEnable( true );
6752
- };
6753
- nv.models.linePlusBarChart = function() {
7011
+ };nv.models.linePlusBarChart = function() {
6754
7012
  "use strict";
6755
7013
 
6756
7014
  //============================================================
@@ -6822,15 +7080,15 @@ nv.models.linePlusBarChart = function() {
6822
7080
  //------------------------------------------------------------
6823
7081
 
6824
7082
  var getBarsAxis = function() {
6825
- return switchYAxisOrder
6826
- ? { main: y1Axis, focus: y3Axis }
6827
- : { main: y2Axis, focus: y4Axis }
7083
+ return !switchYAxisOrder
7084
+ ? { main: y2Axis, focus: y4Axis }
7085
+ : { main: y1Axis, focus: y3Axis }
6828
7086
  }
6829
7087
 
6830
7088
  var getLinesAxis = function() {
6831
- return switchYAxisOrder
6832
- ? { main: y2Axis, focus: y4Axis }
6833
- : { main: y1Axis, focus: y3Axis }
7089
+ return !switchYAxisOrder
7090
+ ? { main: y1Axis, focus: y3Axis }
7091
+ : { main: y2Axis, focus: y4Axis }
6834
7092
  }
6835
7093
 
6836
7094
  var stateGetter = function(data) {
@@ -6900,7 +7158,12 @@ nv.models.linePlusBarChart = function() {
6900
7158
  var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
6901
7159
  var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
6902
7160
 
6903
- x = bars.xScale();
7161
+ if (dataBars.length && !switchYAxisOrder) {
7162
+ x = bars.xScale();
7163
+ } else {
7164
+ x = lines.xScale();
7165
+ }
7166
+
6904
7167
  x2 = x2Axis.scale();
6905
7168
 
6906
7169
  // select the scales and series based on the position of the yAxis
@@ -6959,7 +7222,9 @@ nv.models.linePlusBarChart = function() {
6959
7222
  // Legend
6960
7223
  //------------------------------------------------------------
6961
7224
 
6962
- if (showLegend) {
7225
+ if (!showLegend) {
7226
+ g.select('.nv-legendWrap').selectAll('*').remove();
7227
+ } else {
6963
7228
  var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
6964
7229
  var legendXPosition = legend.align() ? legendWidth : 0;
6965
7230
 
@@ -7234,8 +7499,14 @@ nv.models.linePlusBarChart = function() {
7234
7499
  .tickSize(-availableWidth, 0);
7235
7500
  y2Axis
7236
7501
  .scale(y2)
7237
- ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
7238
- .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
7502
+ ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) );
7503
+
7504
+ // Show the y2 rules only if y1 has none
7505
+ if(!switchYAxisOrder) {
7506
+ y2Axis.tickSize(dataBars.length ? 0 : -availableWidth, 0);
7507
+ } else {
7508
+ y2Axis.tickSize(dataLines.length ? 0 : -availableWidth, 0);
7509
+ }
7239
7510
 
7240
7511
  // Calculate opacity of the axis
7241
7512
  var barsOpacity = dataBars.length ? 1 : 0;
@@ -7379,11 +7650,20 @@ nv.models.linePlusBarChart = function() {
7379
7650
  switchYAxisOrder: {get: function(){return switchYAxisOrder;}, set: function(_){
7380
7651
  // Switch the tick format for the yAxis
7381
7652
  if(switchYAxisOrder !== _) {
7382
- var tickFormat = y1Axis.tickFormat();
7383
- y1Axis.tickFormat(y2Axis.tickFormat());
7384
- y2Axis.tickFormat(tickFormat);
7653
+ var y1 = y1Axis;
7654
+ y1Axis = y2Axis;
7655
+ y2Axis = y1;
7656
+
7657
+ var y3 = y3Axis;
7658
+ y3Axis = y4Axis;
7659
+ y4Axis = y3;
7385
7660
  }
7386
7661
  switchYAxisOrder=_;
7662
+
7663
+ y1Axis.orient('left');
7664
+ y2Axis.orient('right');
7665
+ y3Axis.orient('left');
7666
+ y4Axis.orient('right');
7387
7667
  }}
7388
7668
  });
7389
7669
 
@@ -7495,7 +7775,7 @@ nv.models.multiBar = function() {
7495
7775
  });
7496
7776
 
7497
7777
  // HACK for negative value stacking
7498
- if (stacked) {
7778
+ if (stacked && data.length > 0) {
7499
7779
  data[0].values.map(function(d,i) {
7500
7780
  var posBase = 0, negBase = 0;
7501
7781
  data.map(function(d, idx) {
@@ -7819,7 +8099,8 @@ nv.models.multiBar = function() {
7819
8099
  nv.utils.initOptions(chart);
7820
8100
 
7821
8101
  return chart;
7822
- };nv.models.multiBarChart = function() {
8102
+ };
8103
+ nv.models.multiBarChart = function() {
7823
8104
  "use strict";
7824
8105
 
7825
8106
  //============================================================
@@ -7979,7 +8260,9 @@ nv.models.multiBar = function() {
7979
8260
  gEnter.append('g').attr('class', 'nv-interactive');
7980
8261
 
7981
8262
  // Legend
7982
- if (showLegend) {
8263
+ if (!showLegend) {
8264
+ g.select('.nv-legendWrap').selectAll('*').remove();
8265
+ } else {
7983
8266
  legend.width(availableWidth - controlWidth());
7984
8267
 
7985
8268
  g.select('.nv-legendWrap')
@@ -7996,7 +8279,9 @@ nv.models.multiBar = function() {
7996
8279
  }
7997
8280
 
7998
8281
  // Controls
7999
- if (showControls) {
8282
+ if (!showControls) {
8283
+ g.select('.nv-controlsWrap').selectAll('*').remove();
8284
+ } else {
8000
8285
  var controlsData = [
8001
8286
  { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
8002
8287
  { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
@@ -8480,10 +8765,13 @@ nv.models.multiBarHorizontal = function() {
8480
8765
  });
8481
8766
  })
8482
8767
  .on('click', function(d,i) {
8768
+ var element = this;
8483
8769
  dispatch.elementClick({
8484
8770
  data: d,
8485
8771
  index: i,
8486
- color: d3.select(this).style("fill")
8772
+ color: d3.select(this).style("fill"),
8773
+ event: d3.event,
8774
+ element: element
8487
8775
  });
8488
8776
  d3.event.stopPropagation();
8489
8777
  })
@@ -8807,7 +9095,9 @@ nv.models.multiBarHorizontalChart = function() {
8807
9095
  gEnter.append('g').attr('class', 'nv-controlsWrap');
8808
9096
 
8809
9097
  // Legend
8810
- if (showLegend) {
9098
+ if (!showLegend) {
9099
+ g.select('.nv-legendWrap').selectAll('*').remove();
9100
+ } else {
8811
9101
  legend.width(availableWidth - controlWidth());
8812
9102
 
8813
9103
  g.select('.nv-legendWrap')
@@ -8824,7 +9114,9 @@ nv.models.multiBarHorizontalChart = function() {
8824
9114
  }
8825
9115
 
8826
9116
  // Controls
8827
- if (showControls) {
9117
+ if (!showControls) {
9118
+ g.select('.nv-controlsWrap').selectAll('*').remove();
9119
+ } else {
8828
9120
  var controlsData = [
8829
9121
  { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
8830
9122
  { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
@@ -9044,7 +9336,7 @@ nv.models.multiChart = function() {
9044
9336
  yDomain2,
9045
9337
  getX = function(d) { return d.x },
9046
9338
  getY = function(d) { return d.y},
9047
- interpolate = 'monotone',
9339
+ interpolate = 'linear',
9048
9340
  useVoronoi = true,
9049
9341
  interactiveLayer = nv.interactiveGuideline(),
9050
9342
  useInteractiveGuideline = false,
@@ -9124,7 +9416,7 @@ nv.models.multiChart = function() {
9124
9416
  })
9125
9417
  });
9126
9418
 
9127
- x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return getX(d) }))
9419
+ x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x }))
9128
9420
  .range([0, availableWidth]);
9129
9421
 
9130
9422
  var wrap = container.selectAll('g.wrap.multiChart').data([data]);
@@ -9150,7 +9442,10 @@ nv.models.multiChart = function() {
9150
9442
  return data[i].color || color(d, i);
9151
9443
  });
9152
9444
 
9153
- if (showLegend) {
9445
+ // Legend
9446
+ if (!showLegend) {
9447
+ g.select('.legendWrap').selectAll('*').remove();
9448
+ } else {
9154
9449
  var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
9155
9450
  var legendXPosition = legend.align() ? legendWidth : 0;
9156
9451
 
@@ -9203,10 +9498,12 @@ nv.models.multiChart = function() {
9203
9498
  stack1
9204
9499
  .width(availableWidth)
9205
9500
  .height(availableHeight)
9501
+ .interpolate(interpolate)
9206
9502
  .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
9207
9503
  stack2
9208
9504
  .width(availableWidth)
9209
9505
  .height(availableHeight)
9506
+ .interpolate(interpolate)
9210
9507
  .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
9211
9508
 
9212
9509
  g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
@@ -9323,6 +9620,9 @@ nv.models.multiChart = function() {
9323
9620
  };
9324
9621
  tooltip
9325
9622
  .duration(0)
9623
+ .headerFormatter(function(d, i) {
9624
+ return xAxis.tickFormat()(d, i);
9625
+ })
9326
9626
  .valueFormatter(function(d, i) {
9327
9627
  return yaxis.tickFormat()(d, i);
9328
9628
  })
@@ -9340,6 +9640,9 @@ nv.models.multiChart = function() {
9340
9640
  };
9341
9641
  tooltip
9342
9642
  .duration(100)
9643
+ .headerFormatter(function(d, i) {
9644
+ return xAxis.tickFormat()(d, i);
9645
+ })
9343
9646
  .valueFormatter(function(d, i) {
9344
9647
  return yaxis.tickFormat()(d, i);
9345
9648
  })
@@ -9353,6 +9656,9 @@ nv.models.multiChart = function() {
9353
9656
  evt.point['y'] = stack1.y()(evt.point);
9354
9657
  tooltip
9355
9658
  .duration(0)
9659
+ .headerFormatter(function(d, i) {
9660
+ return xAxis.tickFormat()(d, i);
9661
+ })
9356
9662
  .valueFormatter(function(d, i) {
9357
9663
  return yaxis.tickFormat()(d, i);
9358
9664
  })
@@ -9371,6 +9677,9 @@ nv.models.multiChart = function() {
9371
9677
  };
9372
9678
  tooltip
9373
9679
  .duration(0)
9680
+ .headerFormatter(function(d, i) {
9681
+ return xAxis.tickFormat()(d, i);
9682
+ })
9374
9683
  .valueFormatter(function(d, i) {
9375
9684
  return yaxis.tickFormat()(d, i);
9376
9685
  })
@@ -9433,6 +9742,9 @@ nv.models.multiChart = function() {
9433
9742
 
9434
9743
  interactiveLayer.tooltip
9435
9744
  .chartContainer(chart.container.parentNode)
9745
+ .headerFormatter(function(d, i) {
9746
+ return xAxis.tickFormat()(d, i);
9747
+ })
9436
9748
  .valueFormatter(function(d,i) {
9437
9749
  var yAxis = allData[i].yAxis;
9438
9750
  return d === null ? "N/A" : yAxis.tickFormat()(d);
@@ -9835,8 +10147,11 @@ nv.models.parallelCoordinates = function() {
9835
10147
  var margin = {top: 30, right: 0, bottom: 10, left: 0}
9836
10148
  , width = null
9837
10149
  , height = null
10150
+ , availableWidth = null
10151
+ , availableHeight = null
9838
10152
  , x = d3.scale.ordinal()
9839
10153
  , y = {}
10154
+ , undefinedValuesLabel = "undefined values"
9840
10155
  , dimensionData = []
9841
10156
  , enabledDimensions = []
9842
10157
  , dimensionNames = []
@@ -9859,19 +10174,17 @@ nv.models.parallelCoordinates = function() {
9859
10174
  // Private Variables
9860
10175
  //------------------------------------------------------------
9861
10176
 
9862
-
9863
10177
  var renderWatch = nv.utils.renderWatch(dispatch);
9864
10178
 
9865
10179
  function chart(selection) {
9866
10180
  renderWatch.reset();
9867
10181
  selection.each(function(data) {
9868
10182
  var container = d3.select(this);
9869
- var availableWidth = nv.utils.availableWidth(width, container, margin),
9870
- availableHeight = nv.utils.availableHeight(height, container, margin);
10183
+ availableWidth = nv.utils.availableWidth(width, container, margin);
10184
+ availableHeight = nv.utils.availableHeight(height, container, margin);
9871
10185
 
9872
10186
  nv.utils.initSVG(container);
9873
10187
 
9874
-
9875
10188
  //Convert old data to new format (name, values)
9876
10189
  if (data[0].values === undefined) {
9877
10190
  var newData = [];
@@ -9892,7 +10205,6 @@ nv.models.parallelCoordinates = function() {
9892
10205
  dimensionNames = dimensionData.sort(function (a, b) { return a.currentPosition - b.currentPosition; }).map(function (d) { return d.key });
9893
10206
  enabledDimensions = dimensionData.filter(function (d) { return !d.disabled; });
9894
10207
 
9895
-
9896
10208
  // Setup Scales
9897
10209
  x.rangePoints([0, availableWidth], 1).domain(enabledDimensions.map(function (d) { return d.key; }));
9898
10210
 
@@ -9900,7 +10212,8 @@ nv.models.parallelCoordinates = function() {
9900
10212
  // Extract the list of dimensions and create a scale for each.
9901
10213
  var oldDomainMaxValue = {};
9902
10214
  var displayMissingValuesline = false;
9903
-
10215
+ var currentTicks = [];
10216
+
9904
10217
  dimensionNames.forEach(function(d) {
9905
10218
  var extent = d3.extent(dataValues, function (p) { return +p[d]; });
9906
10219
  var min = extent[0];
@@ -9943,7 +10256,6 @@ nv.models.parallelCoordinates = function() {
9943
10256
  .range([(availableHeight - 12) * 0.9, 0]);
9944
10257
 
9945
10258
  axisWithUndefinedValues = [];
9946
-
9947
10259
  y[d].brush = d3.svg.brush().y(y[d]).on('brushstart', brushstart).on('brush', brush).on('brushend', brushend);
9948
10260
  });
9949
10261
 
@@ -9980,8 +10292,8 @@ nv.models.parallelCoordinates = function() {
9980
10292
  .attr("y2", function(d) { return d[3]; });
9981
10293
 
9982
10294
  //Add the text "undefined values" under the missing value line
9983
- missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data(["undefined values"]);
9984
- missingValueslineText.append('text').data(["undefined values"]);
10295
+ missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data([undefinedValuesLabel]);
10296
+ missingValueslineText.append('text').data([undefinedValuesLabel]);
9985
10297
  missingValueslineText.enter().append('text');
9986
10298
  missingValueslineText.exit().remove();
9987
10299
  missingValueslineText.attr("y", availableHeight)
@@ -10007,7 +10319,9 @@ nv.models.parallelCoordinates = function() {
10007
10319
  d3.select(this).classed('hover', true).style("stroke-width", d.strokeWidth + 2 + "px").style("stroke-opacity", 1);
10008
10320
  dispatch.elementMouseover({
10009
10321
  label: d.name,
10010
- color: d.color || color(d, i)
10322
+ color: d.color || color(d, i),
10323
+ values: d.values,
10324
+ dimensions: enabledDimensions
10011
10325
  });
10012
10326
 
10013
10327
  });
@@ -10035,13 +10349,14 @@ nv.models.parallelCoordinates = function() {
10035
10349
 
10036
10350
  // Add an axis and title.
10037
10351
  dimensionsEnter.append('text')
10038
- .attr('class', 'nv-label')
10352
+ .attr('class', 'nv-label')
10039
10353
  .style("cursor", "move")
10040
10354
  .attr('dy', '-1em')
10041
10355
  .attr('text-anchor', 'middle')
10042
10356
  .on("mouseover", function(d, i) {
10043
10357
  dispatch.elementMouseover({
10044
- label: d.tooltip || d.key
10358
+ label: d.tooltip || d.key,
10359
+ color: d.color
10045
10360
  });
10046
10361
  })
10047
10362
  .on("mouseout", function(d, i) {
@@ -10057,10 +10372,6 @@ nv.models.parallelCoordinates = function() {
10057
10372
  dimensionsEnter.append('g').attr('class', 'nv-brushBackground');
10058
10373
  dimensions.exit().remove();
10059
10374
  dimensions.select('.nv-label').text(function (d) { return d.key });
10060
- dimensions.select('.nv-axis')
10061
- .each(function (d, i) {
10062
- d3.select(this).call(axis.scale(y[d.key]).tickFormat(d3.format(d.format)));
10063
- });
10064
10375
 
10065
10376
  // Add and store a brush for each axis.
10066
10377
  restoreBrush(displayBrush);
@@ -10138,27 +10449,30 @@ nv.models.parallelCoordinates = function() {
10138
10449
  });
10139
10450
 
10140
10451
  dimensions.select('.nv-brushBackground')
10141
- .each(function (d) {
10142
- d3.select(this).call(y[d.key].brush);
10452
+ .each(function (d) {
10453
+ d3.select(this).call(y[d.key].brush);
10143
10454
 
10144
- })
10145
- .selectAll('rect')
10146
- .attr('x', -8)
10147
- .attr('width', 16);
10455
+ })
10456
+ .selectAll('rect')
10457
+ .attr('x', -8)
10458
+ .attr('width', 16);
10459
+
10460
+ updateTicks();
10148
10461
  }
10149
10462
 
10150
10463
  // Handles a brush event, toggling the display of foreground lines.
10151
10464
  function brushstart() {
10152
10465
  //If brush aren't visible, show it before brushing again.
10153
10466
  if (displayBrush === false) {
10467
+ displayBrush = true;
10154
10468
  restoreBrush(true);
10155
10469
  }
10156
10470
  }
10157
10471
 
10158
10472
  // Handles a brush event, toggling the display of foreground lines.
10159
10473
  function brush() {
10160
- actives = dimensionNames.filter(function (p) { return !y[p].brush.empty(); }),
10161
- extents = actives.map(function(p) { return y[p].brush.extent(); });
10474
+ actives = dimensionNames.filter(function (p) { return !y[p].brush.empty(); });
10475
+ extents = actives.map(function(p) { return y[p].brush.extent(); });
10162
10476
 
10163
10477
  filters = []; //erase current filters
10164
10478
  actives.forEach(function(d,i) {
@@ -10179,7 +10493,9 @@ nv.models.parallelCoordinates = function() {
10179
10493
  if (isActive) active.push(d);
10180
10494
  return isActive ? null : 'none';
10181
10495
  });
10182
-
10496
+
10497
+ updateTicks();
10498
+
10183
10499
  dispatch.brush({
10184
10500
  filters: filters,
10185
10501
  active: active
@@ -10194,13 +10510,30 @@ nv.models.parallelCoordinates = function() {
10194
10510
  f.hasOnlyNaN = true;
10195
10511
  });
10196
10512
  dispatch.brushEnd(active, hasActiveBrush);
10513
+ }
10514
+ function updateTicks() {
10515
+ dimensions.select('.nv-axis')
10516
+ .each(function (d, i) {
10517
+ var f = filters.filter(function (k) { return k.dimension == d.key; });
10518
+ currentTicks[d.key] = y[d.key].domain();
10519
+
10520
+ //If brush are available, display brush extent
10521
+ if (f.length != 0 && displayBrush)
10522
+ {
10523
+ currentTicks[d.key] = [];
10524
+ if (f[0].extent[1] > y[d.key].domain()[0])
10525
+ currentTicks[d.key] = [f[0].extent[1]];
10526
+ if (f[0].extent[0] >= y[d.key].domain()[0])
10527
+ currentTicks[d.key].push(f[0].extent[0]);
10528
+ }
10529
+
10530
+ d3.select(this).call(axis.scale(y[d.key]).tickFormat(d.format).tickValues(currentTicks[d.key]));
10531
+ });
10197
10532
  }
10198
10533
  function dragStart(d) {
10199
10534
  dragging[d.key] = this.parentNode.__origin__ = x(d.key);
10200
10535
  background.attr("visibility", "hidden");
10201
-
10202
10536
  }
10203
-
10204
10537
  function dragMove(d) {
10205
10538
  dragging[d.key] = Math.min(availableWidth, Math.max(0, this.parentNode.__origin__ += d3.event.x));
10206
10539
  foreground.attr("d", path);
@@ -10209,7 +10542,6 @@ nv.models.parallelCoordinates = function() {
10209
10542
  x.domain(enabledDimensions.map(function (d) { return d.key; }));
10210
10543
  dimensions.attr("transform", function(d) { return "translate(" + dimensionPosition(d.key) + ")"; });
10211
10544
  }
10212
-
10213
10545
  function dragEnd(d, i) {
10214
10546
  delete this.parentNode.__origin__;
10215
10547
  delete dragging[d.key];
@@ -10222,22 +10554,11 @@ nv.models.parallelCoordinates = function() {
10222
10554
 
10223
10555
  dispatch.dimensionsOrder(enabledDimensions);
10224
10556
  }
10225
- function resetBrush() {
10226
- filters = [];
10227
- active = [];
10228
- dispatch.stateChange();
10229
- }
10230
- function resetDrag() {
10231
- dimensionName.map(function (d, i) { return d.currentPosition = d.originalPosition; });
10232
- dispatch.stateChange();
10233
- }
10234
-
10235
10557
  function dimensionPosition(d) {
10236
10558
  var v = dragging[d];
10237
10559
  return v == null ? x(d) : v;
10238
10560
  }
10239
10561
  });
10240
-
10241
10562
  return chart;
10242
10563
  }
10243
10564
 
@@ -10257,7 +10578,8 @@ nv.models.parallelCoordinates = function() {
10257
10578
  filters: { get: function () { return filters; }, set: function (_) { filters = _; } },
10258
10579
  active: { get: function () { return active; }, set: function (_) { active = _; } },
10259
10580
  lineTension: {get: function(){return lineTension;}, set: function(_){lineTension = _;}},
10260
-
10581
+ undefinedValuesLabel : {get: function(){return undefinedValuesLabel;}, set: function(_){undefinedValuesLabel=_;}},
10582
+
10261
10583
  // deprecated options
10262
10584
  dimensions: {get: function () { return dimensionData.map(function (d){return d.key}); }, set: function (_) {
10263
10585
  // deprecated after 1.8.1
@@ -10267,8 +10589,7 @@ nv.models.parallelCoordinates = function() {
10267
10589
  } else {
10268
10590
  _.forEach(function (k, i) { dimensionData[i].key= k })
10269
10591
  }
10270
- }
10271
- },
10592
+ }},
10272
10593
  dimensionNames: {get: function () { return dimensionData.map(function (d){return d.key}); }, set: function (_) {
10273
10594
  // deprecated after 1.8.1
10274
10595
  nv.deprecated('dimensionNames', 'use dimensionData instead');
@@ -10290,7 +10611,6 @@ nv.models.parallelCoordinates = function() {
10290
10611
  }
10291
10612
 
10292
10613
  }},
10293
-
10294
10614
  // options that require extra logic in the setter
10295
10615
  margin: {get: function(){return margin;}, set: function(_){
10296
10616
  margin.top = _.top !== undefined ? _.top : margin.top;
@@ -10302,7 +10622,6 @@ nv.models.parallelCoordinates = function() {
10302
10622
  color = nv.utils.getColor(_);
10303
10623
  }}
10304
10624
  });
10305
-
10306
10625
  nv.utils.initOptions(chart);
10307
10626
  return chart;
10308
10627
  };
@@ -10324,10 +10643,10 @@ nv.models.parallelCoordinatesChart = function () {
10324
10643
  , color = nv.utils.defaultColor()
10325
10644
  , state = nv.utils.state()
10326
10645
  , dimensionData = []
10327
- , dimensionNames = []
10328
10646
  , displayBrush = true
10329
10647
  , defaultState = null
10330
10648
  , noData = null
10649
+ , nanValue = "undefined"
10331
10650
  , dispatch = d3.dispatch('dimensionsOrder', 'brushEnd', 'stateChange', 'changeState', 'renderEnd')
10332
10651
  , controlWidth = function () { return showControls ? 180 : 0 }
10333
10652
  ;
@@ -10358,6 +10677,20 @@ nv.models.parallelCoordinatesChart = function () {
10358
10677
  }
10359
10678
  };
10360
10679
 
10680
+ tooltip.contentGenerator(function(data) {
10681
+ var str = '<table><thead><tr><td class="legend-color-guide"><div style="background-color:' + data.color + '"></div></td><td><strong>' + data.key + '</strong></td></tr></thead>';
10682
+ if(data.series.length !== 0)
10683
+ {
10684
+ str = str + '<tbody><tr><td height ="10px"></td></tr>';
10685
+ data.series.forEach(function(d){
10686
+ str = str + '<tr><td class="legend-color-guide"><div style="background-color:' + d.color + '"></div></td><td class="key">' + d.key + '</td><td class="value">' + d.value + '</td></tr>';
10687
+ });
10688
+ str = str + '</tbody>';
10689
+ }
10690
+ str = str + '</table>';
10691
+ return str;
10692
+ });
10693
+
10361
10694
  //============================================================
10362
10695
  // Chart function
10363
10696
  //------------------------------------------------------------
@@ -10392,21 +10725,6 @@ nv.models.parallelCoordinatesChart = function () {
10392
10725
  d.currentPosition = isNaN(d.currentPosition) ? i : d.currentPosition;
10393
10726
  });
10394
10727
 
10395
- var currentDimensions = dimensionNames.map(function (d) { return d.key; });
10396
- var newDimensions = dimensionData.map(function (d) { return d.key; });
10397
- dimensionData.forEach(function (k, i) {
10398
- var idx = currentDimensions.indexOf(k.key);
10399
- if (idx < 0) {
10400
- dimensionNames.splice(i, 0, k);
10401
- } else {
10402
- var gap = dimensionNames[idx].currentPosition - dimensionNames[idx].originalPosition;
10403
- dimensionNames[idx].originalPosition = k.originalPosition;
10404
- dimensionNames[idx].currentPosition = k.originalPosition + gap;
10405
- }
10406
- });
10407
- //Remove old dimensions
10408
- dimensionNames = dimensionNames.filter(function (d) { return newDimensions.indexOf(d.key) >= 0; });
10409
-
10410
10728
  if (!defaultState) {
10411
10729
  var key;
10412
10730
  defaultState = {};
@@ -10442,12 +10760,14 @@ nv.models.parallelCoordinatesChart = function () {
10442
10760
  .attr("height", (availableHeight > 0) ? availableHeight : 0);
10443
10761
 
10444
10762
  // Legend
10445
- if (showLegend) {
10763
+ if (!showLegend) {
10764
+ g.select('.nv-legendWrap').selectAll('*').remove();
10765
+ } else {
10446
10766
  legend.width(availableWidth)
10447
10767
  .color(function (d) { return "rgb(188,190,192)"; });
10448
10768
 
10449
10769
  g.select('.nv-legendWrap')
10450
- .datum(dimensionNames.sort(function (a, b) { return a.originalPosition - b.originalPosition; }))
10770
+ .datum(dimensionData.sort(function (a, b) { return a.originalPosition - b.originalPosition; }))
10451
10771
  .call(legend);
10452
10772
 
10453
10773
  if (margin.top != legend.height()) {
@@ -10459,14 +10779,11 @@ nv.models.parallelCoordinatesChart = function () {
10459
10779
  }
10460
10780
  wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10461
10781
 
10462
-
10463
-
10464
-
10465
10782
  // Main Chart Component(s)
10466
10783
  parallelCoordinates
10467
10784
  .width(availableWidth)
10468
10785
  .height(availableHeight)
10469
- .dimensionData(dimensionNames)
10786
+ .dimensionData(dimensionData)
10470
10787
  .displayBrush(displayBrush);
10471
10788
 
10472
10789
  var parallelCoordinatesWrap = g.select('.nv-parallelCoordinatesWrap ')
@@ -10498,21 +10815,21 @@ nv.models.parallelCoordinatesChart = function () {
10498
10815
 
10499
10816
  //Update dimensions order and display reset sorting button
10500
10817
  parallelCoordinates.dispatch.on('dimensionsOrder', function (e) {
10501
- dimensionNames.sort(function (a, b) { return a.currentPosition - b.currentPosition; });
10818
+ dimensionData.sort(function (a, b) { return a.currentPosition - b.currentPosition; });
10502
10819
  var isSorted = false;
10503
- dimensionNames.forEach(function (d, i) {
10820
+ dimensionData.forEach(function (d, i) {
10504
10821
  d.currentPosition = i;
10505
10822
  if (d.currentPosition !== d.originalPosition)
10506
10823
  isSorted = true;
10507
10824
  });
10508
- dispatch.dimensionsOrder(dimensionNames, isSorted);
10825
+ dispatch.dimensionsOrder(dimensionData, isSorted);
10509
10826
  });
10510
10827
 
10511
10828
  // Update chart from a state object passed to event handler
10512
10829
  dispatch.on('changeState', function (e) {
10513
10830
 
10514
10831
  if (typeof e.disabled !== 'undefined') {
10515
- dimensionNames.forEach(function (series, i) {
10832
+ dimensionData.forEach(function (series, i) {
10516
10833
  series.disabled = e.disabled[i];
10517
10834
  });
10518
10835
  state.disabled = e.disabled;
@@ -10530,11 +10847,27 @@ nv.models.parallelCoordinatesChart = function () {
10530
10847
  //------------------------------------------------------------
10531
10848
 
10532
10849
  parallelCoordinates.dispatch.on('elementMouseover.tooltip', function (evt) {
10533
- evt['series'] = {
10850
+ var tp = {
10534
10851
  key: evt.label,
10535
- color: evt.color
10536
- };
10537
- tooltip.data(evt).hidden(false);
10852
+ color: evt.color,
10853
+ series: []
10854
+ }
10855
+ if(evt.values){
10856
+ Object.keys(evt.values).forEach(function (d) {
10857
+ var dim = evt.dimensions.filter(function (dd) {return dd.key === d;})[0];
10858
+ if(dim){
10859
+ var v;
10860
+ if (isNaN(evt.values[d]) || isNaN(parseFloat(evt.values[d]))) {
10861
+ v = nanValue;
10862
+ } else {
10863
+ v = dim.format(evt.values[d]);
10864
+ }
10865
+ tp.series.push({ idx: dim.currentPosition, key: d, value: v, color: dim.color });
10866
+ }
10867
+ });
10868
+ tp.series.sort(function(a,b) {return a.idx - b.idx});
10869
+ }
10870
+ tooltip.data(tp).hidden(false);
10538
10871
  });
10539
10872
 
10540
10873
  parallelCoordinates.dispatch.on('elementMouseout.tooltip', function(evt) {
@@ -10553,7 +10886,6 @@ nv.models.parallelCoordinatesChart = function () {
10553
10886
  chart.parallelCoordinates = parallelCoordinates;
10554
10887
  chart.legend = legend;
10555
10888
  chart.tooltip = tooltip;
10556
-
10557
10889
  chart.options = nv.utils.optionsFunc.bind(chart);
10558
10890
 
10559
10891
  chart._options = Object.create({}, {
@@ -10565,7 +10897,8 @@ nv.models.parallelCoordinatesChart = function () {
10565
10897
  dimensionData: { get: function () { return dimensionData; }, set: function (_) { dimensionData = _; } },
10566
10898
  displayBrush: { get: function () { return displayBrush; }, set: function (_) { displayBrush = _; } },
10567
10899
  noData: { get: function () { return noData; }, set: function (_) { noData = _; } },
10568
-
10900
+ nanValue: { get: function () { return nanValue; }, set: function (_) { nanValue = _; } },
10901
+
10569
10902
  // options that require extra logic in the setter
10570
10903
  margin: {
10571
10904
  get: function () { return margin; },
@@ -10587,7 +10920,8 @@ nv.models.parallelCoordinatesChart = function () {
10587
10920
  nv.utils.initOptions(chart);
10588
10921
 
10589
10922
  return chart;
10590
- };nv.models.pie = function() {
10923
+ };
10924
+ nv.models.pie = function() {
10591
10925
  "use strict";
10592
10926
 
10593
10927
  //============================================================
@@ -10649,9 +10983,15 @@ nv.models.parallelCoordinatesChart = function () {
10649
10983
  arcsRadiusInner.push(inner);
10650
10984
  }
10651
10985
  } else {
10652
- arcsRadiusOuter = arcsRadius.map(function (d) { return (d.outer - d.outer / 5) * radius; });
10653
- arcsRadiusInner = arcsRadius.map(function (d) { return (d.inner - d.inner / 5) * radius; });
10654
- donutRatio = d3.min(arcsRadius.map(function (d) { return (d.inner - d.inner / 5); }));
10986
+ if(growOnHover){
10987
+ arcsRadiusOuter = arcsRadius.map(function (d) { return (d.outer - d.outer / 5) * radius; });
10988
+ arcsRadiusInner = arcsRadius.map(function (d) { return (d.inner - d.inner / 5) * radius; });
10989
+ donutRatio = d3.min(arcsRadius.map(function (d) { return (d.inner - d.inner / 5); }));
10990
+ } else {
10991
+ arcsRadiusOuter = arcsRadius.map(function (d) { return d.outer * radius; });
10992
+ arcsRadiusInner = arcsRadius.map(function (d) { return d.inner * radius; });
10993
+ donutRatio = d3.min(arcsRadius.map(function (d) { return d.inner; }));
10994
+ }
10655
10995
  }
10656
10996
  nv.utils.initSVG(container);
10657
10997
 
@@ -11119,7 +11459,9 @@ nv.models.pieChart = function() {
11119
11459
  gEnter.append('g').attr('class', 'nv-legendWrap');
11120
11460
 
11121
11461
  // Legend
11122
- if (showLegend) {
11462
+ if (!showLegend) {
11463
+ g.select('.nv-legendWrap').selectAll('*').remove();
11464
+ } else {
11123
11465
  if (legendPosition === "top") {
11124
11466
  legend.width( availableWidth ).key(pie.x());
11125
11467
 
@@ -11219,6 +11561,8 @@ nv.models.pieChart = function() {
11219
11561
  // use Object get/set functionality to map between vars and chart functions
11220
11562
  chart._options = Object.create({}, {
11221
11563
  // simple options, just get/set the necessary values
11564
+ width: {get: function(){return width;}, set: function(_){width=_;}},
11565
+ height: {get: function(){return height;}, set: function(_){height=_;}},
11222
11566
  noData: {get: function(){return noData;}, set: function(_){noData=_;}},
11223
11567
  showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
11224
11568
  legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}},
@@ -11288,6 +11632,7 @@ nv.models.scatter = function() {
11288
11632
  , useVoronoi = true
11289
11633
  , duration = 250
11290
11634
  , interactiveUpdateDelay = 300
11635
+ , showLabels = false
11291
11636
  ;
11292
11637
 
11293
11638
 
@@ -11300,8 +11645,34 @@ nv.models.scatter = function() {
11300
11645
  , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
11301
11646
  , renderWatch = nv.utils.renderWatch(dispatch, duration)
11302
11647
  , _sizeRange_def = [16, 256]
11648
+ , _caches
11303
11649
  ;
11304
11650
 
11651
+ function getCache(d) {
11652
+ var cache, i;
11653
+ cache = _caches = _caches || {};
11654
+ i = d[0].series;
11655
+ cache = cache[i] = cache[i] || {};
11656
+ i = d[1];
11657
+ cache = cache[i] = cache[i] || {};
11658
+ return cache;
11659
+ }
11660
+
11661
+ function getDiffs(d) {
11662
+ var i, key,
11663
+ point = d[0],
11664
+ cache = getCache(d),
11665
+ diffs = false;
11666
+ for (i = 1; i < arguments.length; i ++) {
11667
+ key = arguments[i];
11668
+ if (cache[key] !== point[key] || !cache.hasOwnProperty(key)) {
11669
+ cache[key] = point[key];
11670
+ diffs = true;
11671
+ }
11672
+ }
11673
+ return diffs;
11674
+ }
11675
+
11305
11676
  function chart(selection) {
11306
11677
  renderWatch.reset();
11307
11678
  selection.each(function(data) {
@@ -11319,6 +11690,7 @@ nv.models.scatter = function() {
11319
11690
  });
11320
11691
 
11321
11692
  // Setup Scales
11693
+ var logScale = chart.yScale().name === d3.scale.log().name ? true : false;
11322
11694
  // remap and flatten the data for use in calculating the scales' domains
11323
11695
  var seriesData = (xDomain && yDomain && sizeDomain) ? [] : // if we know xDomain and yDomain and sizeDomain, no need to calculate.... if Size is constant remember to set sizeDomain to speed up performance
11324
11696
  d3.merge(
@@ -11337,7 +11709,7 @@ nv.models.scatter = function() {
11337
11709
  else
11338
11710
  x.range(xRange || [0, availableWidth]);
11339
11711
 
11340
- if (chart.yScale().name === "o") {
11712
+ if (logScale) {
11341
11713
  var min = d3.min(seriesData.map(function(d) { if (d.y !== 0) return d.y; }));
11342
11714
  y.clamp(true)
11343
11715
  .domain(yDomain || d3.extent(seriesData.map(function(d) {
@@ -11378,6 +11750,8 @@ nv.models.scatter = function() {
11378
11750
  y0 = y0 || y;
11379
11751
  z0 = z0 || z;
11380
11752
 
11753
+ var scaleDiff = x(1) !== x0(1) || y(1) !== y0(1) || z(1) !== z0(1);
11754
+
11381
11755
  // Setup containers and skeleton of chart
11382
11756
  var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
11383
11757
  var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id);
@@ -11421,8 +11795,8 @@ nv.models.scatter = function() {
11421
11795
  var pX = getX(point,pointIndex);
11422
11796
  var pY = getY(point,pointIndex);
11423
11797
 
11424
- return [x(pX)+ Math.random() * 1e-4,
11425
- y(pY)+ Math.random() * 1e-4,
11798
+ return [nv.utils.NaNtoZero(x(pX))+ Math.random() * 1e-4,
11799
+ nv.utils.NaNtoZero(y(pY))+ Math.random() * 1e-4,
11426
11800
  groupIndex,
11427
11801
  pointIndex, point]; //temp hack to add noise until I think of a better way so there are no duplicates
11428
11802
  })
@@ -11621,6 +11995,7 @@ nv.models.scatter = function() {
11621
11995
  .attr('class', function(d,i) {
11622
11996
  return (d.classed || '') + ' nv-group nv-series-' + i;
11623
11997
  })
11998
+ .classed('nv-noninteractive', !interactive)
11624
11999
  .classed('hover', function(d) { return d.hover });
11625
12000
  groups.watchTransition(renderWatch, 'scatter: groups')
11626
12001
  .style('fill', function(d,i) { return color(d, i) })
@@ -11640,6 +12015,9 @@ nv.models.scatter = function() {
11640
12015
  })
11641
12016
  });
11642
12017
  points.enter().append('path')
12018
+ .attr('class', function (d) {
12019
+ return 'nv-point nv-point-' + d[1];
12020
+ })
11643
12021
  .style('fill', function (d) { return d.color })
11644
12022
  .style('stroke', function (d) { return d.color })
11645
12023
  .attr('transform', function(d) {
@@ -11657,25 +12035,66 @@ nv.models.scatter = function() {
11657
12035
  return 'translate(' + nv.utils.NaNtoZero(x(getX(d[0],d[1]))) + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')'
11658
12036
  })
11659
12037
  .remove();
11660
- points.each(function(d) {
11661
- d3.select(this)
11662
- .classed('nv-point', true)
11663
- .classed('nv-point-' + d[1], true)
11664
- .classed('nv-noninteractive', !interactive)
11665
- .classed('hover',false)
11666
- ;
11667
- });
11668
- points
12038
+ points.filter(function (d) { return scaleDiff || getDiffs(d, 'x', 'y'); })
11669
12039
  .watchTransition(renderWatch, 'scatter points')
11670
12040
  .attr('transform', function(d) {
11671
12041
  //nv.log(d, getX(d[0],d[1]), x(getX(d[0],d[1])));
11672
12042
  return 'translate(' + nv.utils.NaNtoZero(x(getX(d[0],d[1]))) + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')'
11673
- })
12043
+ });
12044
+ points.filter(function (d) { return scaleDiff || getDiffs(d, 'shape', 'size'); })
12045
+ .watchTransition(renderWatch, 'scatter points')
11674
12046
  .attr('d',
11675
12047
  nv.utils.symbol()
11676
12048
  .type(function(d) { return getShape(d[0]); })
11677
12049
  .size(function(d) { return z(getSize(d[0],d[1])) })
11678
12050
  );
12051
+
12052
+ // add label a label to scatter chart
12053
+ if(showLabels)
12054
+ {
12055
+ var titles = groups.selectAll('.nv-label')
12056
+ .data(function(d) {
12057
+ return d.values.map(
12058
+ function (point, pointIndex) {
12059
+ return [point, pointIndex]
12060
+ }).filter(
12061
+ function(pointArray, pointIndex) {
12062
+ return pointActive(pointArray[0], pointIndex)
12063
+ })
12064
+ });
12065
+
12066
+ titles.enter().append('text')
12067
+ .style('fill', function (d,i) {
12068
+ return d.color })
12069
+ .style('stroke-opacity', 0)
12070
+ .style('fill-opacity', 1)
12071
+ .attr('transform', function(d) {
12072
+ var dx = nv.utils.NaNtoZero(x0(getX(d[0],d[1]))) + Math.sqrt(z(getSize(d[0],d[1]))/Math.PI) + 2;
12073
+ return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y0(getY(d[0],d[1]))) + ')';
12074
+ })
12075
+ .text(function(d,i){
12076
+ return d[0].label;});
12077
+
12078
+ titles.exit().remove();
12079
+ groups.exit().selectAll('path.nv-label')
12080
+ .watchTransition(renderWatch, 'scatter exit')
12081
+ .attr('transform', function(d) {
12082
+ var dx = nv.utils.NaNtoZero(x(getX(d[0],d[1])))+ Math.sqrt(z(getSize(d[0],d[1]))/Math.PI)+2;
12083
+ return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')';
12084
+ })
12085
+ .remove();
12086
+ titles.each(function(d) {
12087
+ d3.select(this)
12088
+ .classed('nv-label', true)
12089
+ .classed('nv-label-' + d[1], false)
12090
+ .classed('hover',false);
12091
+ });
12092
+ titles.watchTransition(renderWatch, 'scatter labels')
12093
+ .attr('transform', function(d) {
12094
+ var dx = nv.utils.NaNtoZero(x(getX(d[0],d[1])))+ Math.sqrt(z(getSize(d[0],d[1]))/Math.PI)+2;
12095
+ return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')'
12096
+ });
12097
+ }
11679
12098
 
11680
12099
  // Delay updating the invisible interactive layer for smoother animation
11681
12100
  if( interactiveUpdateDelay )
@@ -11758,7 +12177,7 @@ nv.models.scatter = function() {
11758
12177
  showVoronoi: {get: function(){return showVoronoi;}, set: function(_){showVoronoi=_;}},
11759
12178
  id: {get: function(){return id;}, set: function(_){id=_;}},
11760
12179
  interactiveUpdateDelay: {get:function(){return interactiveUpdateDelay;}, set: function(_){interactiveUpdateDelay=_;}},
11761
-
12180
+ showLabels: {get: function(){return showLabels;}, set: function(_){ showLabels = _;}},
11762
12181
 
11763
12182
  // simple functor options
11764
12183
  x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
@@ -11826,6 +12245,7 @@ nv.models.scatterChart = function() {
11826
12245
  , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
11827
12246
  , noData = null
11828
12247
  , duration = 250
12248
+ , showLabels = false
11829
12249
  ;
11830
12250
 
11831
12251
  scatter.xScale(x).yScale(y);
@@ -11947,7 +12367,9 @@ nv.models.scatterChart = function() {
11947
12367
  }
11948
12368
 
11949
12369
  // Legend
11950
- if (showLegend) {
12370
+ if (!showLegend) {
12371
+ g.select('.nv-legendWrap').selectAll('*').remove();
12372
+ } else {
11951
12373
  var legendWidth = availableWidth;
11952
12374
  legend.width(legendWidth);
11953
12375
 
@@ -11973,7 +12395,8 @@ nv.models.scatterChart = function() {
11973
12395
  .color(data.map(function(d,i) {
11974
12396
  d.color = d.color || color(d, i);
11975
12397
  return d.color;
11976
- }).filter(function(d,i) { return !data[i].disabled }));
12398
+ }).filter(function(d,i) { return !data[i].disabled }))
12399
+ .showLabels(showLabels);
11977
12400
 
11978
12401
  wrap.select('.nv-scatterWrap')
11979
12402
  .datum(data.filter(function(d) { return !d.disabled }))
@@ -12151,6 +12574,7 @@ nv.models.scatterChart = function() {
12151
12574
  defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
12152
12575
  noData: {get: function(){return noData;}, set: function(_){noData=_;}},
12153
12576
  duration: {get: function(){return duration;}, set: function(_){duration=_;}},
12577
+ showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
12154
12578
 
12155
12579
  // options that require extra logic in the setter
12156
12580
  margin: {get: function(){return margin;}, set: function(_){
@@ -12197,6 +12621,8 @@ nv.models.sparkline = function() {
12197
12621
  , yDomain
12198
12622
  , xRange
12199
12623
  , yRange
12624
+ , showMinMaxPoints = true
12625
+ , showCurrentPoint = true
12200
12626
  , dispatch = d3.dispatch('renderEnd')
12201
12627
  ;
12202
12628
 
@@ -12257,7 +12683,7 @@ nv.models.sparkline = function() {
12257
12683
  var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
12258
12684
  minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
12259
12685
  currentPoint = pointIndex(yValues.length - 1);
12260
- return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;});
12686
+ return [(showMinMaxPoints ? minPoint : null), (showMinMaxPoints ? maxPoint : null), (showCurrentPoint ? currentPoint : null)].filter(function (d) {return d != null;});
12261
12687
  });
12262
12688
  points.enter().append('circle');
12263
12689
  points.exit().remove();
@@ -12283,15 +12709,17 @@ nv.models.sparkline = function() {
12283
12709
 
12284
12710
  chart._options = Object.create({}, {
12285
12711
  // simple options, just get/set the necessary values
12286
- width: {get: function(){return width;}, set: function(_){width=_;}},
12287
- height: {get: function(){return height;}, set: function(_){height=_;}},
12288
- xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
12289
- yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
12290
- xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
12291
- yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
12292
- xScale: {get: function(){return x;}, set: function(_){x=_;}},
12293
- yScale: {get: function(){return y;}, set: function(_){y=_;}},
12294
- animate: {get: function(){return animate;}, set: function(_){animate=_;}},
12712
+ width: {get: function(){return width;}, set: function(_){width=_;}},
12713
+ height: {get: function(){return height;}, set: function(_){height=_;}},
12714
+ xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
12715
+ yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
12716
+ xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
12717
+ yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
12718
+ xScale: {get: function(){return x;}, set: function(_){x=_;}},
12719
+ yScale: {get: function(){return y;}, set: function(_){y=_;}},
12720
+ animate: {get: function(){return animate;}, set: function(_){animate=_;}},
12721
+ showMinMaxPoints: {get: function(){return showMinMaxPoints;}, set: function(_){showMinMaxPoints=_;}},
12722
+ showCurrentPoint: {get: function(){return showCurrentPoint;}, set: function(_){showCurrentPoint=_;}},
12295
12723
 
12296
12724
  //functor options
12297
12725
  x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}},
@@ -12646,7 +13074,6 @@ nv.models.stackedArea = function() {
12646
13074
  .y(function(d) {
12647
13075
  if (d.display !== undefined) { return d.display.y + d.display.y0; }
12648
13076
  })
12649
- .forceY([0])
12650
13077
  .color(data.map(function(d,i) {
12651
13078
  d.color = d.color || color(d, d.seriesIndex);
12652
13079
  return d.color;
@@ -13021,7 +13448,9 @@ nv.models.stackedAreaChart = function() {
13021
13448
  g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
13022
13449
 
13023
13450
  // Legend
13024
- if (showLegend) {
13451
+ if (!showLegend) {
13452
+ g.select('.nv-legendWrap').selectAll('*').remove();
13453
+ } else {
13025
13454
  var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
13026
13455
 
13027
13456
  legend.width(legendWidth);
@@ -13037,7 +13466,9 @@ nv.models.stackedAreaChart = function() {
13037
13466
  }
13038
13467
 
13039
13468
  // Controls
13040
- if (showControls) {
13469
+ if (!showControls) {
13470
+ g.select('.nv-controlsWrap').selectAll('*').remove();
13471
+ } else {
13041
13472
  var controlsData = [
13042
13473
  {
13043
13474
  key: controlLabels.stacked || 'Stacked',
@@ -13231,7 +13662,7 @@ nv.models.stackedAreaChart = function() {
13231
13662
  key: series.key,
13232
13663
  value: tooltipValue,
13233
13664
  color: color(series,series.seriesIndex),
13234
- stackedValue: point.display
13665
+ point: point
13235
13666
  });
13236
13667
 
13237
13668
  if (showTotalInTooltip && stacked.style() != 'expand') {
@@ -13250,8 +13681,8 @@ nv.models.stackedAreaChart = function() {
13250
13681
  //To handle situation where the stacked area chart is negative, we need to use absolute values
13251
13682
  //when checking if the mouse Y value is within the stack area.
13252
13683
  yValue = Math.abs(yValue);
13253
- var stackedY0 = Math.abs(series.stackedValue.y0);
13254
- var stackedY = Math.abs(series.stackedValue.y);
13684
+ var stackedY0 = Math.abs(series.point.display.y0);
13685
+ var stackedY = Math.abs(series.point.display.y);
13255
13686
  if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0))
13256
13687
  {
13257
13688
  indexToHighlight = i;
@@ -13423,70 +13854,208 @@ nv.models.sunburst = function() {
13423
13854
  //------------------------------------------------------------
13424
13855
 
13425
13856
  var margin = {top: 0, right: 0, bottom: 0, left: 0}
13426
- , width = null
13427
- , height = null
13857
+ , width = 600
13858
+ , height = 600
13428
13859
  , mode = "count"
13429
- , modes = {count: function(d) { return 1; }, size: function(d) { return d.size }}
13860
+ , modes = {count: function(d) { return 1; }, value: function(d) { return d.value || d.size }, size: function(d) { return d.value || d.size }}
13430
13861
  , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
13431
13862
  , container = null
13432
13863
  , color = nv.utils.defaultColor()
13864
+ , showLabels = false
13865
+ , labelFormat = function(d){if(mode === 'count'){return d.name + ' #' + d.value}else{return d.name + ' ' + (d.value || d.size)}}
13866
+ , labelThreshold = 0.02
13867
+ , sort = function(d1, d2){return d1.name > d2.name;}
13868
+ , key = function(d,i){return d.name;}
13433
13869
  , groupColorByParent = true
13434
13870
  , duration = 500
13435
- , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMousemove', 'elementMouseover', 'elementMouseout', 'renderEnd')
13436
- ;
13871
+ , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMousemove', 'elementMouseover', 'elementMouseout', 'renderEnd');
13872
+
13873
+ //============================================================
13874
+ // aux functions and setup
13875
+ //------------------------------------------------------------
13437
13876
 
13438
13877
  var x = d3.scale.linear().range([0, 2 * Math.PI]);
13439
13878
  var y = d3.scale.sqrt();
13440
13879
 
13441
- var partition = d3.layout.partition()
13442
- .sort(null)
13443
- .value(function(d) { return 1; });
13880
+ var partition = d3.layout.partition().sort(sort);
13881
+
13882
+ var node, availableWidth, availableHeight, radius;
13883
+ var prevPositions = {};
13444
13884
 
13445
13885
  var arc = d3.svg.arc()
13446
- .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
13447
- .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
13448
- .innerRadius(function(d) { return Math.max(0, y(d.y)); })
13449
- .outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
13886
+ .startAngle(function(d) {return Math.max(0, Math.min(2 * Math.PI, x(d.x))) })
13887
+ .endAngle(function(d) {return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))) })
13888
+ .innerRadius(function(d) {return Math.max(0, y(d.y)) })
13889
+ .outerRadius(function(d) {return Math.max(0, y(d.y + d.dy)) });
13890
+
13891
+ function rotationToAvoidUpsideDown(d) {
13892
+ var centerAngle = computeCenterAngle(d);
13893
+ if(centerAngle > 90){
13894
+ return 180;
13895
+ }
13896
+ else {
13897
+ return 0;
13898
+ }
13899
+ }
13900
+
13901
+ function computeCenterAngle(d) {
13902
+ var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x)));
13903
+ var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
13904
+ var centerAngle = (((startAngle + endAngle) / 2) * (180 / Math.PI)) - 90;
13905
+ return centerAngle;
13906
+ }
13907
+
13908
+ function labelThresholdMatched(d) {
13909
+ var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x)));
13910
+ var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
13911
+
13912
+ var size = endAngle - startAngle;
13913
+ return size > labelThreshold;
13914
+ }
13915
+
13916
+ // When zooming: interpolate the scales.
13917
+ function arcTweenZoom(e,i) {
13918
+ var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]),
13919
+ yd = d3.interpolate(y.domain(), [node.y, 1]),
13920
+ yr = d3.interpolate(y.range(), [node.y ? 20 : 0, radius]);
13921
+
13922
+ if (i === 0) {
13923
+ return function() {return arc(e);}
13924
+ }
13925
+ else {
13926
+ return function (t) {
13927
+ x.domain(xd(t));
13928
+ y.domain(yd(t)).range(yr(t));
13929
+ return arc(e);
13930
+ }
13931
+ };
13932
+ }
13933
+
13934
+ function arcTweenUpdate(d) {
13935
+ var ipo = d3.interpolate({x: d.x0, dx: d.dx0, y: d.y0, dy: d.dy0}, d);
13936
+
13937
+ return function (t) {
13938
+ var b = ipo(t);
13939
+
13940
+ d.x0 = b.x;
13941
+ d.dx0 = b.dx;
13942
+ d.y0 = b.y;
13943
+ d.dy0 = b.dy;
13944
+
13945
+ return arc(b);
13946
+ };
13947
+ }
13948
+
13949
+ function updatePrevPosition(node) {
13950
+ var k = key(node);
13951
+ if(! prevPositions[k]) prevPositions[k] = {};
13952
+ var pP = prevPositions[k];
13953
+ pP.dx = node.dx;
13954
+ pP.x = node.x;
13955
+ pP.dy = node.dy;
13956
+ pP.y = node.y;
13957
+ }
13958
+
13959
+ function storeRetrievePrevPositions(nodes) {
13960
+ nodes.forEach(function(n){
13961
+ var k = key(n);
13962
+ var pP = prevPositions[k];
13963
+ //console.log(k,n,pP);
13964
+ if( pP ){
13965
+ n.dx0 = pP.dx;
13966
+ n.x0 = pP.x;
13967
+ n.dy0 = pP.dy;
13968
+ n.y0 = pP.y;
13969
+ }
13970
+ else {
13971
+ n.dx0 = n.dx;
13972
+ n.x0 = n.x;
13973
+ n.dy0 = n.dy;
13974
+ n.y0 = n.y;
13975
+ }
13976
+ updatePrevPosition(n);
13977
+ });
13978
+ }
13450
13979
 
13451
- // Keep track of the current and previous node being displayed as the root.
13452
- var node, prevNode;
13453
- // Keep track of the root node
13454
- var rootNode;
13980
+ function zoomClick(d) {
13981
+ var labels = container.selectAll('text')
13982
+ var path = container.selectAll('path')
13983
+
13984
+ // fade out all text elements
13985
+ labels.transition().attr("opacity",0);
13986
+
13987
+ // to allow reference to the new center node
13988
+ node = d;
13989
+
13990
+ path.transition()
13991
+ .duration(duration)
13992
+ .attrTween("d", arcTweenZoom)
13993
+ .each('end', function(e) {
13994
+ // partially taken from here: http://bl.ocks.org/metmajer/5480307
13995
+ // check if the animated element's data e lies within the visible angle span given in d
13996
+ if(e.x >= d.x && e.x < (d.x + d.dx) ){
13997
+ if(e.depth >= d.depth){
13998
+ // get a selection of the associated text element
13999
+ var parentNode = d3.select(this.parentNode);
14000
+ var arcText = parentNode.select('text');
14001
+
14002
+ // fade in the text element and recalculate positions
14003
+ arcText.transition().duration(duration)
14004
+ .text( function(e){return labelFormat(e) })
14005
+ .attr("opacity", function(d){
14006
+ if(labelThresholdMatched(d)) {
14007
+ return 1;
14008
+ }
14009
+ else {
14010
+ return 0;
14011
+ }
14012
+ })
14013
+ .attr("transform", function() {
14014
+ var width = this.getBBox().width;
14015
+ if(e.depth === 0)
14016
+ return "translate(" + (width / 2 * - 1) + ",0)";
14017
+ else if(e.depth === d.depth){
14018
+ return "translate(" + (y(e.y) + 5) + ",0)";
14019
+ }
14020
+ else {
14021
+ var centerAngle = computeCenterAngle(e);
14022
+ var rotation = rotationToAvoidUpsideDown(e);
14023
+ if (rotation === 0) {
14024
+ return 'rotate('+ centerAngle +')translate(' + (y(e.y) + 5) + ',0)';
14025
+ }
14026
+ else {
14027
+ return 'rotate('+ centerAngle +')translate(' + (y(e.y) + width + 5) + ',0)rotate(' + rotation + ')';
14028
+ }
14029
+ }
14030
+ });
14031
+ }
14032
+ }
14033
+ })
14034
+ }
13455
14035
 
13456
14036
  //============================================================
13457
14037
  // chart function
13458
14038
  //------------------------------------------------------------
13459
-
13460
14039
  var renderWatch = nv.utils.renderWatch(dispatch);
13461
14040
 
13462
14041
  function chart(selection) {
13463
14042
  renderWatch.reset();
14043
+
13464
14044
  selection.each(function(data) {
13465
14045
  container = d3.select(this);
13466
- var availableWidth = nv.utils.availableWidth(width, container, margin);
13467
- var availableHeight = nv.utils.availableHeight(height, container, margin);
13468
- var radius = Math.min(availableWidth, availableHeight) / 2;
13469
- var path;
14046
+ availableWidth = nv.utils.availableWidth(width, container, margin);
14047
+ availableHeight = nv.utils.availableHeight(height, container, margin);
14048
+ radius = Math.min(availableWidth, availableHeight) / 2;
13470
14049
 
13471
- nv.utils.initSVG(container);
14050
+ y.range([0, radius]);
13472
14051
 
13473
14052
  // Setup containers and skeleton of chart
13474
- var wrap = container.selectAll('.nv-wrap.nv-sunburst').data(data);
13475
- var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburst nv-chart-' + id);
13476
-
13477
- var g = wrapEnter.selectAll('nv-sunburst');
13478
-
13479
- chart.update = function() {
13480
- if ( duration === 0 ) {
13481
- container.call(chart);
13482
- } else {
13483
- container.transition().duration(duration).call(chart);
13484
- }
13485
- };
13486
- chart.container = this;
13487
-
13488
-
13489
- wrap.attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
14053
+ var wrap = container.select('g.nvd3.nv-wrap.nv-sunburst');
14054
+ if( !wrap[0][0] ) {
14055
+ wrap = container.append('g')
14056
+ .attr('class', 'nvd3 nv-wrap nv-sunburst nv-chart-' + id)
14057
+ .attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
14058
+ }
13490
14059
 
13491
14060
  container.on('click', function (d, i) {
13492
14061
  dispatch.chartClick({
@@ -13497,13 +14066,21 @@ nv.models.sunburst = function() {
13497
14066
  });
13498
14067
  });
13499
14068
 
13500
- y.range([0, radius]);
13501
-
13502
- node = node || data;
13503
- rootNode = data[0];
13504
14069
  partition.value(modes[mode] || modes["count"]);
13505
- path = g.data(partition.nodes).enter()
13506
- .append("path")
14070
+
14071
+ //reverse the drawing order so that the labels of inner
14072
+ //arcs are drawn on top of the outer arcs.
14073
+ var nodes = partition.nodes(data[0]).reverse()
14074
+
14075
+ storeRetrievePrevPositions(nodes);
14076
+ var cG = wrap.selectAll('.arc-container').data(nodes, key)
14077
+
14078
+ //handle new datapoints
14079
+ var cGE = cG.enter()
14080
+ .append("g")
14081
+ .attr("class",'arc-container')
14082
+
14083
+ cGE.append("path")
13507
14084
  .attr("d", arc)
13508
14085
  .style("fill", function (d) {
13509
14086
  if (d.color) {
@@ -13517,22 +14094,7 @@ nv.models.sunburst = function() {
13517
14094
  }
13518
14095
  })
13519
14096
  .style("stroke", "#FFF")
13520
- .on("click", function(d) {
13521
- if (prevNode !== node && node !== d) prevNode = node;
13522
- node = d;
13523
- path.transition()
13524
- .duration(duration)
13525
- .attrTween("d", arcTweenZoom(d));
13526
- })
13527
- .each(stash)
13528
- .on("dblclick", function(d) {
13529
- if (prevNode.parent == d) {
13530
- path.transition()
13531
- .duration(duration)
13532
- .attrTween("d", arcTweenZoom(rootNode));
13533
- }
13534
- })
13535
- .each(stash)
14097
+ .on("click", zoomClick)
13536
14098
  .on('mouseover', function(d,i){
13537
14099
  d3.select(this).classed('hover', true).style('opacity', 0.8);
13538
14100
  dispatch.elementMouseover({
@@ -13552,58 +14114,68 @@ nv.models.sunburst = function() {
13552
14114
  });
13553
14115
  });
13554
14116
 
14117
+ ///Iterating via each and selecting based on the this
14118
+ ///makes it work ... a cG.selectAll('path') doesn't.
14119
+ ///Without iteration the data (in the element) didn't update.
14120
+ cG.each(function(d){
14121
+ d3.select(this).select('path')
14122
+ .transition()
14123
+ .duration(duration)
14124
+ .attrTween('d', arcTweenUpdate);
14125
+ });
13555
14126
 
14127
+ if(showLabels){
14128
+ //remove labels first and add them back
14129
+ cG.selectAll('text').remove();
13556
14130
 
13557
- // Setup for switching data: stash the old values for transition.
13558
- function stash(d) {
13559
- d.x0 = d.x;
13560
- d.dx0 = d.dx;
14131
+ //this way labels are on top of newly added arcs
14132
+ cG.append('text')
14133
+ .text( function(e){ return labelFormat(e)})
14134
+ .transition()
14135
+ .duration(duration)
14136
+ .attr("opacity", function(d){
14137
+ if(labelThresholdMatched(d)) {
14138
+ return 1;
14139
+ }
14140
+ else {
14141
+ return 0;
14142
+ }
14143
+ })
14144
+ .attr("transform", function(d) {
14145
+ var width = this.getBBox().width;
14146
+ if(d.depth === 0){
14147
+ return "rotate(0)translate(" + (width / 2 * -1) + ",0)";
14148
+ }
14149
+ else {
14150
+ var centerAngle = computeCenterAngle(d);
14151
+ var rotation = rotationToAvoidUpsideDown(d);
14152
+ if (rotation === 0) {
14153
+ return 'rotate('+ centerAngle +')translate(' + (y(d.y) + 5) + ',0)';
14154
+ }
14155
+ else {
14156
+ return 'rotate('+ centerAngle +')translate(' + (y(d.y) + width + 5) + ',0)rotate(' + rotation + ')';
14157
+ }
14158
+ }
14159
+ });
13561
14160
  }
13562
14161
 
13563
- // When switching data: interpolate the arcs in data space.
13564
- function arcTweenData(a, i) {
13565
- var oi = d3.interpolate({x: a.x0, dx: a.dx0}, a);
13566
-
13567
- function tween(t) {
13568
- var b = oi(t);
13569
- a.x0 = b.x;
13570
- a.dx0 = b.dx;
13571
- return arc(b);
13572
- }
13573
-
13574
- if (i == 0) {
13575
- // If we are on the first arc, adjust the x domain to match the root node
13576
- // at the current zoom level. (We only need to do this once.)
13577
- var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]);
13578
- return function (t) {
13579
- x.domain(xd(t));
13580
- return tween(t);
13581
- };
13582
- } else {
13583
- return tween;
13584
- }
13585
- }
14162
+ //zoom out to the center when the data is updated.
14163
+ zoomClick(nodes[nodes.length - 1])
13586
14164
 
13587
- // When zooming: interpolate the scales.
13588
- function arcTweenZoom(d) {
13589
- var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
13590
- yd = d3.interpolate(y.domain(), [d.y, 1]),
13591
- yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
13592
- return function (d, i) {
13593
- return i
13594
- ? function (t) {
13595
- return arc(d);
13596
- }
13597
- : function (t) {
13598
- x.domain(xd(t));
13599
- y.domain(yd(t)).range(yr(t));
13600
- return arc(d);
13601
- };
13602
- };
13603
- }
13604
14165
 
14166
+ //remove unmatched elements ...
14167
+ cG.exit()
14168
+ .transition()
14169
+ .duration(duration)
14170
+ .attr('opacity',0)
14171
+ .each('end',function(d){
14172
+ var k = key(d);
14173
+ prevPositions[k] = undefined;
14174
+ })
14175
+ .remove();
13605
14176
  });
13606
14177
 
14178
+
13607
14179
  renderWatch.renderEnd('sunburst immediate');
13608
14180
  return chart;
13609
14181
  }
@@ -13623,7 +14195,11 @@ nv.models.sunburst = function() {
13623
14195
  id: {get: function(){return id;}, set: function(_){id=_;}},
13624
14196
  duration: {get: function(){return duration;}, set: function(_){duration=_;}},
13625
14197
  groupColorByParent: {get: function(){return groupColorByParent;}, set: function(_){groupColorByParent=!!_;}},
13626
-
14198
+ showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=!!_}},
14199
+ labelFormat: {get: function(){return labelFormat;}, set: function(_){labelFormat=_}},
14200
+ labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_}},
14201
+ sort: {get: function(){return sort;}, set: function(_){sort=_}},
14202
+ key: {get: function(){return key;}, set: function(_){key=_}},
13627
14203
  // options that require extra logic in the setter
13628
14204
  margin: {get: function(){return margin;}, set: function(_){
13629
14205
  margin.top = _.top != undefined ? _.top : margin.top;
@@ -13657,21 +14233,19 @@ nv.models.sunburstChart = function() {
13657
14233
  , defaultState = null
13658
14234
  , noData = null
13659
14235
  , duration = 250
13660
- , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
13661
- ;
14236
+ , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd');
13662
14237
 
13663
- tooltip.duration(0);
13664
14238
 
13665
14239
  //============================================================
13666
14240
  // Private Variables
13667
14241
  //------------------------------------------------------------
13668
14242
 
13669
14243
  var renderWatch = nv.utils.renderWatch(dispatch);
14244
+
13670
14245
  tooltip
14246
+ .duration(0)
13671
14247
  .headerEnabled(false)
13672
- .valueFormatter(function(d, i) {
13673
- return d;
13674
- });
14248
+ .valueFormatter(function(d){return d;});
13675
14249
 
13676
14250
  //============================================================
13677
14251
  // Chart function
@@ -13683,11 +14257,11 @@ nv.models.sunburstChart = function() {
13683
14257
 
13684
14258
  selection.each(function(data) {
13685
14259
  var container = d3.select(this);
14260
+
13686
14261
  nv.utils.initSVG(container);
13687
14262
 
13688
- var that = this;
13689
- var availableWidth = nv.utils.availableWidth(width, container, margin),
13690
- availableHeight = nv.utils.availableHeight(height, container, margin);
14263
+ var availableWidth = nv.utils.availableWidth(width, container, margin);
14264
+ var availableHeight = nv.utils.availableHeight(height, container, margin);
13691
14265
 
13692
14266
  chart.update = function() {
13693
14267
  if (duration === 0) {
@@ -13696,7 +14270,7 @@ nv.models.sunburstChart = function() {
13696
14270
  container.transition().duration(duration).call(chart);
13697
14271
  }
13698
14272
  };
13699
- chart.container = this;
14273
+ chart.container = container;
13700
14274
 
13701
14275
  // Display No Data message if there's nothing to show.
13702
14276
  if (!data || !data.length) {
@@ -13706,20 +14280,8 @@ nv.models.sunburstChart = function() {
13706
14280
  container.selectAll('.nv-noData').remove();
13707
14281
  }
13708
14282
 
13709
- // Setup containers and skeleton of chart
13710
- var wrap = container.selectAll('g.nv-wrap.nv-sunburstChart').data(data);
13711
- var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburstChart').append('g');
13712
- var g = wrap.select('g');
13713
-
13714
- gEnter.append('g').attr('class', 'nv-sunburstWrap');
13715
-
13716
- wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
13717
-
13718
- // Main Chart Component(s)
13719
14283
  sunburst.width(availableWidth).height(availableHeight);
13720
- var sunWrap = g.select('.nv-sunburstWrap').datum(data);
13721
- d3.transition(sunWrap).call(sunburst);
13722
-
14284
+ container.call(sunburst);
13723
14285
  });
13724
14286
 
13725
14287
  renderWatch.renderEnd('sunburstChart immediate');
@@ -13731,9 +14293,9 @@ nv.models.sunburstChart = function() {
13731
14293
  //------------------------------------------------------------
13732
14294
 
13733
14295
  sunburst.dispatch.on('elementMouseover.tooltip', function(evt) {
13734
- evt['series'] = {
14296
+ evt.series = {
13735
14297
  key: evt.data.name,
13736
- value: evt.data.size,
14298
+ value: (evt.data.value || evt.data.size),
13737
14299
  color: evt.color
13738
14300
  };
13739
14301
  tooltip.data(evt).hidden(false);
@@ -13783,7 +14345,8 @@ nv.models.sunburstChart = function() {
13783
14345
  nv.utils.inheritOptions(chart, sunburst);
13784
14346
  nv.utils.initOptions(chart);
13785
14347
  return chart;
14348
+
13786
14349
  };
13787
14350
 
13788
- nv.version = "1.8.2";
14351
+ nv.version = "1.8.3";
13789
14352
  })();