nv.d3.growingMultiBar.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. // Licensed to Cloudera, Inc. under one
  2. // or more contributor license agreements. See the NOTICE file
  3. // distributed with this work for additional information
  4. // regarding copyright ownership. Cloudera, Inc. licenses this file
  5. // to you under the Apache License, Version 2.0 (the
  6. // "License"); you may not use this file except in compliance
  7. // with the License. You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. nv.models.growingMultiBar = function() {
  17. "use strict";
  18. //============================================================
  19. // Public Variables with Default Settings
  20. //------------------------------------------------------------
  21. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  22. , width = 960
  23. , height = 500
  24. , x = d3.scale.ordinal()
  25. , y = d3.scale.linear()
  26. , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
  27. , getX = function(d) { return d.x }
  28. , getY = function(d) { return d.y }
  29. , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
  30. , clipEdge = true
  31. , stacked = false
  32. , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function
  33. , color = nv.utils.defaultColor()
  34. , hideable = false
  35. , barColor = null // adding the ability to set the color for each rather than the whole group
  36. , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
  37. , delay = 1200
  38. , xDomain
  39. , yDomain
  40. , xRange
  41. , yRange
  42. , groupSpacing = 0.1
  43. , selectBars = null
  44. , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
  45. ;
  46. //============================================================
  47. //============================================================
  48. // Private Variables
  49. //------------------------------------------------------------
  50. var x0, y0 //used to store previous scales
  51. ;
  52. //============================================================
  53. function chart(selection) {
  54. selection.each(function(data) {
  55. var availableWidth = width - margin.left - margin.right,
  56. availableHeight = height - margin.top - margin.bottom,
  57. container = d3.select(this);
  58. if(hideable && data.length) hideable = [{
  59. values: data[0].values.map(function(d) {
  60. return {
  61. x: d.x,
  62. y: 0,
  63. series: d.series,
  64. size: 0.01
  65. };}
  66. )}];
  67. if (stacked)
  68. data = d3.layout.stack()
  69. .offset(stackOffset)
  70. .values(function(d){ return d.values })
  71. .y(getY)
  72. (!data.length && hideable ? hideable : data);
  73. //add series index to each data point for reference
  74. data.forEach(function(series, i) {
  75. series.values.forEach(function(point) {
  76. point.series = i;
  77. });
  78. });
  79. //------------------------------------------------------------
  80. // HACK for negative value stacking
  81. if (stacked)
  82. data[0].values.map(function(d,i) {
  83. var posBase = 0, negBase = 0;
  84. data.map(function(d) {
  85. var f = d.values[i]
  86. f.size = Math.abs(f.y);
  87. if (f.y<0) {
  88. f.y1 = negBase;
  89. negBase = negBase - f.size;
  90. } else
  91. {
  92. f.y1 = f.size + posBase;
  93. posBase = posBase + f.size;
  94. }
  95. });
  96. });
  97. //------------------------------------------------------------
  98. // Setup Scales
  99. // remap and flatten the data for use in calculating the scales' domains
  100. var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
  101. data.map(function(d) {
  102. return d.values.map(function(d,i) {
  103. return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
  104. })
  105. });
  106. x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
  107. .rangeBands(xRange || [0, availableWidth], groupSpacing);
  108. //y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y1 : 0) }).concat(forceY)))
  109. y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 : d.y1 + d.y ) : d.y }).concat(forceY)))
  110. .range(yRange || [availableHeight, 0]);
  111. // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
  112. if (x.domain()[0] === x.domain()[1])
  113. x.domain()[0] ?
  114. x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
  115. : x.domain([-1,1]);
  116. if (y.domain()[0] === y.domain()[1])
  117. y.domain()[0] ?
  118. y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
  119. : y.domain([-1,1]);
  120. x0 = x0 || x;
  121. y0 = y0 || y;
  122. //------------------------------------------------------------
  123. //------------------------------------------------------------
  124. // Setup containers and skeleton of chart
  125. var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
  126. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
  127. var defsEnter = wrapEnter.append('defs');
  128. var gEnter = wrapEnter.append('g');
  129. var g = wrap.select('g')
  130. gEnter.append('g').attr('class', 'nv-groups');
  131. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  132. //------------------------------------------------------------
  133. defsEnter.append('clipPath')
  134. .attr('id', 'nv-edge-clip-' + id)
  135. .append('rect');
  136. wrap.select('#nv-edge-clip-' + id + ' rect')
  137. .attr('width', availableWidth)
  138. .attr('height', availableHeight);
  139. g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
  140. var groups = wrap.select('.nv-groups').selectAll('.nv-group')
  141. .data(function(d) { return d }, function(d,i) { return i });
  142. groups.enter().append('g')
  143. .style('stroke-opacity', 1e-6)
  144. .style('fill-opacity', 1e-6);
  145. groups.exit()
  146. .transition()
  147. .selectAll('rect.nv-bar')
  148. .delay(function(d,i) {
  149. return i * delay/ data[0].values.length;
  150. })
  151. .attr('y', function(d) { return stacked ? y0(d.y0) : y0(0) })
  152. .attr('height', 0)
  153. .remove();
  154. groups
  155. .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
  156. .classed('hover', function(d) { return d.hover })
  157. .style('fill', function(d,i){ return color(d, i) })
  158. .style('stroke', function(d,i){ return color(d, i) });
  159. groups
  160. .transition()
  161. .style('stroke-opacity', 1)
  162. .style('fill-opacity', .75);
  163. var bars = groups.selectAll('rect.nv-bar')
  164. .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
  165. bars.exit().remove();
  166. selectBars = function(selected) {
  167. $(selected).each(function(cnt, item){
  168. bars.each(function(d, i) {
  169. if (d.x instanceof Date){
  170. if (moment(d.x).utc().format("YYYY-MM-DD[T]HH:mm:ss[Z]") == item) {
  171. d3.select(this).classed('selected', true);
  172. }
  173. }
  174. else {
  175. if (d.x == item) {
  176. d3.select(this).classed('selected', true);
  177. }
  178. }
  179. });
  180. });
  181. };
  182. var barsEnter = bars.enter().append('rect')
  183. .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
  184. .attr('x', function(d,i,j) {
  185. return stacked ? 0 : (j * x.rangeBand() / data.length )
  186. })
  187. .attr('y', function(d) { return y0(stacked ? d.y0 : 0) })
  188. .attr('height', 0)
  189. .attr('width', x.rangeBand() / (stacked ? 1 : data.length) )
  190. .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
  191. ;
  192. bars
  193. .style('fill', function(d,i,j){ return color(d, j, i); })
  194. .style('stroke', function(d,i,j){ return color(d, j, i); });
  195. barsEnter
  196. .on('mouseover', function(d,i) {
  197. d3.select(this).classed('hover', true);
  198. dispatch.elementMouseover({
  199. value: getY(d,i),
  200. point: d,
  201. series: data[d.series],
  202. pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
  203. pointIndex: i,
  204. seriesIndex: d.series,
  205. e: d3.event
  206. });
  207. })
  208. .on('mouseout', function(d,i) {
  209. d3.select(this).classed('hover', false);
  210. dispatch.elementMouseout({
  211. value: getY(d,i),
  212. point: d,
  213. series: data[d.series],
  214. pointIndex: i,
  215. seriesIndex: d.series,
  216. e: d3.event
  217. });
  218. })
  219. .on('click', function(d,i) {
  220. dispatch.elementClick({
  221. value: getY(d,i),
  222. point: d,
  223. series: data[d.series],
  224. pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
  225. pointIndex: i,
  226. seriesIndex: d.series,
  227. e: d3.event
  228. });
  229. d3.event.stopPropagation();
  230. })
  231. .on('dblclick', function(d,i) {
  232. dispatch.elementDblClick({
  233. value: getY(d,i),
  234. point: d,
  235. series: data[d.series],
  236. pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
  237. pointIndex: i,
  238. seriesIndex: d.series,
  239. e: d3.event
  240. });
  241. d3.event.stopPropagation();
  242. });
  243. bars
  244. .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
  245. .transition()
  246. .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
  247. if (barColor) {
  248. if (!disabled) disabled = data.map(function() { return true });
  249. bars
  250. .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); })
  251. .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); });
  252. }
  253. if (stacked)
  254. bars.transition()
  255. .delay(function(d,i) {
  256. return i * delay / data[0].values.length;
  257. })
  258. .attr('y', function(d,i) {
  259. return y((stacked ? d.y1 : 0));
  260. })
  261. .attr('height', function(d,i) {
  262. return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1);
  263. })
  264. .attr('x', function(d,i) {
  265. return stacked ? 0 : (d.series * x.rangeBand() / data.length )
  266. })
  267. .attr('width', x.rangeBand() / (stacked ? 1 : data.length) );
  268. else
  269. bars.transition()
  270. .delay(function(d,i) {
  271. return i * delay/ data[0].values.length;
  272. })
  273. .attr('x', function(d,i) {
  274. return d.series * x.rangeBand() / data.length
  275. })
  276. .attr('width', x.rangeBand() / data.length)
  277. .attr('y', function(d,i) {
  278. return getY(d,i) < 0 ?
  279. y(0) :
  280. y(0) - y(getY(d,i)) < 1 ?
  281. y(0) - 1 :
  282. y(getY(d,i)) || 0;
  283. })
  284. .attr('height', function(d,i) {
  285. return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
  286. });
  287. //store old scales for use in transitions on update
  288. x0 = x.copy();
  289. y0 = y.copy();
  290. });
  291. return chart;
  292. }
  293. //============================================================
  294. // Expose Public Variables
  295. //------------------------------------------------------------
  296. chart.dispatch = dispatch;
  297. chart.options = nv.utils.optionsFunc.bind(chart);
  298. chart.x = function(_) {
  299. if (!arguments.length) return getX;
  300. getX = _;
  301. return chart;
  302. };
  303. chart.y = function(_) {
  304. if (!arguments.length) return getY;
  305. getY = _;
  306. return chart;
  307. };
  308. chart.margin = function(_) {
  309. if (!arguments.length) return margin;
  310. margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
  311. margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
  312. margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
  313. margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
  314. return chart;
  315. };
  316. chart.width = function(_) {
  317. if (!arguments.length) return width;
  318. width = _;
  319. return chart;
  320. };
  321. chart.height = function(_) {
  322. if (!arguments.length) return height;
  323. height = _;
  324. return chart;
  325. };
  326. chart.xScale = function(_) {
  327. if (!arguments.length) return x;
  328. x = _;
  329. return chart;
  330. };
  331. chart.yScale = function(_) {
  332. if (!arguments.length) return y;
  333. y = _;
  334. return chart;
  335. };
  336. chart.xDomain = function(_) {
  337. if (!arguments.length) return xDomain;
  338. xDomain = _;
  339. return chart;
  340. };
  341. chart.yDomain = function(_) {
  342. if (!arguments.length) return yDomain;
  343. yDomain = _;
  344. return chart;
  345. };
  346. chart.xRange = function(_) {
  347. if (!arguments.length) return xRange;
  348. xRange = _;
  349. return chart;
  350. };
  351. chart.yRange = function(_) {
  352. if (!arguments.length) return yRange;
  353. yRange = _;
  354. return chart;
  355. };
  356. chart.forceY = function(_) {
  357. if (!arguments.length) return forceY;
  358. forceY = _;
  359. return chart;
  360. };
  361. chart.stacked = function(_) {
  362. if (!arguments.length) return stacked;
  363. stacked = _;
  364. return chart;
  365. };
  366. chart.stackOffset = function(_) {
  367. if (!arguments.length) return stackOffset;
  368. stackOffset = _;
  369. return chart;
  370. };
  371. chart.clipEdge = function(_) {
  372. if (!arguments.length) return clipEdge;
  373. clipEdge = _;
  374. return chart;
  375. };
  376. chart.color = function(_) {
  377. if (!arguments.length) return color;
  378. color = nv.utils.getColor(_);
  379. return chart;
  380. };
  381. chart.barColor = function(_) {
  382. if (!arguments.length) return barColor;
  383. barColor = nv.utils.getColor(_);
  384. return chart;
  385. };
  386. chart.disabled = function(_) {
  387. if (!arguments.length) return disabled;
  388. disabled = _;
  389. return chart;
  390. };
  391. chart.id = function(_) {
  392. if (!arguments.length) return id;
  393. id = _;
  394. return chart;
  395. };
  396. chart.hideable = function(_) {
  397. if (!arguments.length) return hideable;
  398. hideable = _;
  399. return chart;
  400. };
  401. chart.delay = function(_) {
  402. if (!arguments.length) return delay;
  403. delay = _;
  404. return chart;
  405. };
  406. chart.groupSpacing = function(_) {
  407. if (!arguments.length) return groupSpacing;
  408. groupSpacing = _;
  409. return chart;
  410. };
  411. chart.selectBars = function(args) {
  412. if (!arguments.length) return selectBars;
  413. selectBars(args);
  414. return chart;
  415. };
  416. //============================================================
  417. return chart;
  418. }