nv.d3.growingMultiBar.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  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', nv.utils.NaNtoZero(availableHeight) >= 0 ? nv.utils.NaNtoZero(availableHeight) : 0);
  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. var _pivotField = null;
  168. if (!Array.isArray(selected)){
  169. _pivotField = selected.field;
  170. selected = selected.selected;
  171. }
  172. $(selected).each(function(cnt, item){
  173. bars.each(function(d, i) {
  174. if (_pivotField != null){
  175. if (d.obj.fq_fields == _pivotField && d.obj.fq_values == item){
  176. d3.select(this).classed('selected', true);
  177. }
  178. }
  179. else {
  180. if (d.x instanceof Date){
  181. if (moment(d.x).utc().format("YYYY-MM-DD[T]HH:mm:ss[Z]") == item) {
  182. d3.select(this).classed('selected', true);
  183. }
  184. }
  185. else {
  186. if (d.x == item) {
  187. d3.select(this).classed('selected', true);
  188. }
  189. }
  190. }
  191. });
  192. });
  193. };
  194. var barsEnter = bars.enter().append('rect')
  195. .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
  196. .attr('x', function(d,i,j) {
  197. return stacked ? 0 : (j * x.rangeBand() / data.length )
  198. })
  199. .attr('y', function(d) { return y0(stacked ? d.y0 : 0) })
  200. .attr('height', 0)
  201. .attr('width', x.rangeBand() / (stacked ? 1 : data.length) )
  202. .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
  203. ;
  204. bars
  205. .style('fill', function(d,i,j){ return color(d, j, i); })
  206. .style('stroke', function(d,i,j){ return color(d, j, i); });
  207. barsEnter
  208. .on('mouseover', function(d,i) {
  209. d3.select(this).classed('hover', true);
  210. dispatch.elementMouseover({
  211. value: getY(d,i),
  212. point: d,
  213. series: data[d.series],
  214. 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
  215. pointIndex: i,
  216. seriesIndex: d.series,
  217. e: d3.event
  218. });
  219. })
  220. .on('mouseout', function(d,i) {
  221. d3.select(this).classed('hover', false);
  222. dispatch.elementMouseout({
  223. value: getY(d,i),
  224. point: d,
  225. series: data[d.series],
  226. pointIndex: i,
  227. seriesIndex: d.series,
  228. e: d3.event
  229. });
  230. })
  231. .on('click', function(d,i) {
  232. dispatch.elementClick({
  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. .on('dblclick', function(d,i) {
  244. dispatch.elementDblClick({
  245. value: getY(d,i),
  246. point: d,
  247. series: data[d.series],
  248. 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
  249. pointIndex: i,
  250. seriesIndex: d.series,
  251. e: d3.event
  252. });
  253. d3.event.stopPropagation();
  254. });
  255. bars
  256. .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
  257. .transition()
  258. .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
  259. if (barColor) {
  260. if (!disabled) disabled = data.map(function() { return true });
  261. bars
  262. .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(); })
  263. .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(); });
  264. }
  265. if (stacked)
  266. bars.transition()
  267. .delay(function(d,i) {
  268. return i * delay / data[0].values.length;
  269. })
  270. .attr('y', function(d,i) {
  271. return y((stacked ? d.y1 : 0));
  272. })
  273. .attr('height', function(d,i) {
  274. return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1);
  275. })
  276. .attr('x', function(d,i) {
  277. return stacked ? 0 : (d.series * x.rangeBand() / data.length )
  278. })
  279. .attr('width', x.rangeBand() / (stacked ? 1 : data.length) );
  280. else
  281. bars.transition()
  282. .delay(function(d,i) {
  283. return i * delay/ data[0].values.length;
  284. })
  285. .attr('x', function(d,i) {
  286. return d.series * x.rangeBand() / data.length
  287. })
  288. .attr('width', x.rangeBand() / data.length)
  289. .attr('y', function(d,i) {
  290. return getY(d,i) < 0 ?
  291. y(0) :
  292. y(0) - y(getY(d,i)) < 1 ?
  293. y(0) - 1 :
  294. y(getY(d,i)) || 0;
  295. })
  296. .attr('height', function(d,i) {
  297. return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
  298. });
  299. //store old scales for use in transitions on update
  300. x0 = x.copy();
  301. y0 = y.copy();
  302. });
  303. return chart;
  304. }
  305. //============================================================
  306. // Expose Public Variables
  307. //------------------------------------------------------------
  308. chart.dispatch = dispatch;
  309. chart.options = nv.utils.optionsFunc.bind(chart);
  310. chart.x = function(_) {
  311. if (!arguments.length) return getX;
  312. getX = _;
  313. return chart;
  314. };
  315. chart.y = function(_) {
  316. if (!arguments.length) return getY;
  317. getY = _;
  318. return chart;
  319. };
  320. chart.margin = function(_) {
  321. if (!arguments.length) return margin;
  322. margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
  323. margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
  324. margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
  325. margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
  326. return chart;
  327. };
  328. chart.width = function(_) {
  329. if (!arguments.length) return width;
  330. width = _;
  331. return chart;
  332. };
  333. chart.height = function(_) {
  334. if (!arguments.length) return height;
  335. height = _;
  336. return chart;
  337. };
  338. chart.xScale = function(_) {
  339. if (!arguments.length) return x;
  340. x = _;
  341. return chart;
  342. };
  343. chart.yScale = function(_) {
  344. if (!arguments.length) return y;
  345. y = _;
  346. return chart;
  347. };
  348. chart.xDomain = function(_) {
  349. if (!arguments.length) return xDomain;
  350. xDomain = _;
  351. return chart;
  352. };
  353. chart.yDomain = function(_) {
  354. if (!arguments.length) return yDomain;
  355. yDomain = _;
  356. return chart;
  357. };
  358. chart.xRange = function(_) {
  359. if (!arguments.length) return xRange;
  360. xRange = _;
  361. return chart;
  362. };
  363. chart.yRange = function(_) {
  364. if (!arguments.length) return yRange;
  365. yRange = _;
  366. return chart;
  367. };
  368. chart.forceY = function(_) {
  369. if (!arguments.length) return forceY;
  370. forceY = _;
  371. return chart;
  372. };
  373. chart.stacked = function(_) {
  374. if (!arguments.length) return stacked;
  375. stacked = _;
  376. return chart;
  377. };
  378. chart.stackOffset = function(_) {
  379. if (!arguments.length) return stackOffset;
  380. stackOffset = _;
  381. return chart;
  382. };
  383. chart.clipEdge = function(_) {
  384. if (!arguments.length) return clipEdge;
  385. clipEdge = _;
  386. return chart;
  387. };
  388. chart.color = function(_) {
  389. if (!arguments.length) return color;
  390. color = nv.utils.getColor(_);
  391. return chart;
  392. };
  393. chart.barColor = function(_) {
  394. if (!arguments.length) return barColor;
  395. barColor = nv.utils.getColor(_);
  396. return chart;
  397. };
  398. chart.disabled = function(_) {
  399. if (!arguments.length) return disabled;
  400. disabled = _;
  401. return chart;
  402. };
  403. chart.id = function(_) {
  404. if (!arguments.length) return id;
  405. id = _;
  406. return chart;
  407. };
  408. chart.hideable = function(_) {
  409. if (!arguments.length) return hideable;
  410. hideable = _;
  411. return chart;
  412. };
  413. chart.delay = function(_) {
  414. if (!arguments.length) return delay;
  415. delay = _;
  416. return chart;
  417. };
  418. chart.groupSpacing = function(_) {
  419. if (!arguments.length) return groupSpacing;
  420. groupSpacing = _;
  421. return chart;
  422. };
  423. chart.selectBars = function(args) {
  424. if (!arguments.length) return selectBars;
  425. selectBars(args);
  426. return chart;
  427. };
  428. //============================================================
  429. return chart;
  430. }