nv.d3.scatterChart.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  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.scatterChart = function() {
  17. "use strict";
  18. //============================================================
  19. // Public Variables with Default Settings
  20. //------------------------------------------------------------
  21. var scatter = nv.models.scatter()
  22. , xAxis = nv.models.axis()
  23. , yAxis = nv.models.axis()
  24. , legend = nv.models.legend()
  25. , controls = nv.models.legend()
  26. , distX = nv.models.distribution()
  27. , distY = nv.models.distribution()
  28. ;
  29. var margin = {top: 30, right: 20, bottom: 50, left: 75}
  30. , width = null
  31. , height = null
  32. , color = nv.utils.defaultColor()
  33. , x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale()
  34. , y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale()
  35. , xPadding = 0
  36. , yPadding = 0
  37. , showDistX = false
  38. , showDistY = false
  39. , showLegend = true
  40. , showXAxis = true
  41. , showYAxis = true
  42. , rightAlignYAxis = false
  43. , showControls = !!d3.fisheye
  44. , fisheye = 0
  45. , pauseFisheye = false
  46. , tooltips = true
  47. , tooltipX = function(key, x, y) { return '<strong>' + x + '</strong>' }
  48. , tooltipY = function(key, x, y) { return '<strong>' + y + '</strong>' }
  49. , tooltip = null
  50. , state = {}
  51. , defaultState = null
  52. , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
  53. , noData = "No Data Available."
  54. , transitionDuration = 250
  55. ;
  56. scatter
  57. .xScale(x)
  58. .yScale(y)
  59. ;
  60. xAxis
  61. .orient('bottom')
  62. .tickPadding(10)
  63. ;
  64. yAxis
  65. .orient((rightAlignYAxis) ? 'right' : 'left')
  66. .tickPadding(10)
  67. ;
  68. distX
  69. .axis('x')
  70. ;
  71. distY
  72. .axis('y')
  73. ;
  74. controls.updateState(false);
  75. //============================================================
  76. //============================================================
  77. // Private Variables
  78. //------------------------------------------------------------
  79. var x0, y0;
  80. var showTooltip = function(e, offsetElement) {
  81. //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?)
  82. var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
  83. top = e.pos[1] + ( offsetElement.offsetTop || 0),
  84. leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
  85. topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
  86. leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
  87. topY = e.pos[1] + ( offsetElement.offsetTop || 0),
  88. xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
  89. yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
  90. if( tooltipX != null )
  91. nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
  92. if( tooltipY != null )
  93. nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
  94. if( tooltip != null )
  95. nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
  96. };
  97. var controlsData = [
  98. { key: 'Magnify', disabled: true }
  99. ];
  100. //============================================================
  101. function chart(selection) {
  102. selection.each(function(data) {
  103. var container = d3.select(this),
  104. that = this;
  105. var availableWidth = (width || parseInt(container.style('width')) || 960)
  106. - margin.left - margin.right,
  107. availableHeight = (height || parseInt(container.style('height')) || 400)
  108. - margin.top - margin.bottom;
  109. chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
  110. chart.container = this;
  111. //set state.disabled
  112. state.disabled = data.map(function(d) { return !!d.disabled });
  113. if (!defaultState) {
  114. var key;
  115. defaultState = {};
  116. for (key in state) {
  117. if (state[key] instanceof Array)
  118. defaultState[key] = state[key].slice(0);
  119. else
  120. defaultState[key] = state[key];
  121. }
  122. }
  123. //------------------------------------------------------------
  124. // Display noData message if there's nothing to show.
  125. if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
  126. var noDataText = container.selectAll('.nv-noData').data([noData]);
  127. noDataText.enter().append('text')
  128. .attr('class', 'nvd3 nv-noData')
  129. .attr('dy', '-.7em')
  130. .style('text-anchor', 'middle');
  131. noDataText
  132. .attr('x', margin.left + availableWidth / 2)
  133. .attr('y', margin.top + availableHeight / 2)
  134. .text(function(d) { return d });
  135. return chart;
  136. } else {
  137. container.selectAll('.nv-noData').remove();
  138. }
  139. //------------------------------------------------------------
  140. //------------------------------------------------------------
  141. // Setup Scales
  142. x0 = x0 || x;
  143. y0 = y0 || y;
  144. //------------------------------------------------------------
  145. //------------------------------------------------------------
  146. // Setup containers and skeleton of chart
  147. var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
  148. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
  149. var gEnter = wrapEnter.append('g');
  150. var g = wrap.select('g');
  151. // background for pointer events
  152. gEnter.append('rect').attr('class', 'nvd3 nv-background');
  153. gEnter.append('g').attr('class', 'nv-x nv-axis');
  154. gEnter.append('g').attr('class', 'nv-y nv-axis');
  155. gEnter.append('g').attr('class', 'nv-scatterWrap');
  156. gEnter.append('g').attr('class', 'nv-distWrap');
  157. gEnter.append('g').attr('class', 'nv-legendWrap');
  158. gEnter.append('g').attr('class', 'nv-controlsWrap');
  159. //------------------------------------------------------------
  160. //------------------------------------------------------------
  161. // Legend
  162. if (showLegend) {
  163. var legendWidth = (showControls) ? availableWidth / 2 : availableWidth;
  164. legend.width(legendWidth);
  165. wrap.select('.nv-legendWrap')
  166. .datum(data)
  167. .call(legend);
  168. if ( margin.top != legend.height()) {
  169. margin.top = legend.height();
  170. availableHeight = (height || parseInt(container.style('height')) || 400)
  171. - margin.top - margin.bottom;
  172. }
  173. wrap.select('.nv-legendWrap')
  174. .attr('transform', 'translate(' + (availableWidth - legendWidth) + ',' + (-margin.top) +')');
  175. }
  176. //------------------------------------------------------------
  177. //------------------------------------------------------------
  178. // Controls
  179. if (showControls) {
  180. controls.width(180).color(['#444']);
  181. g.select('.nv-controlsWrap')
  182. .datum(controlsData)
  183. .attr('transform', 'translate(0,' + (-margin.top) +')')
  184. .call(controls);
  185. }
  186. //------------------------------------------------------------
  187. wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
  188. if (rightAlignYAxis) {
  189. g.select(".nv-y.nv-axis")
  190. .attr("transform", "translate(" + availableWidth + ",0)");
  191. }
  192. //------------------------------------------------------------
  193. // Main Chart Component(s)
  194. scatter
  195. .width(availableWidth)
  196. .height(availableHeight)
  197. .color(data.map(function(d,i) {
  198. return d.color || color(d, i);
  199. }).filter(function(d,i) { return !data[i].disabled }));
  200. if (xPadding !== 0)
  201. scatter.xDomain(null);
  202. if (yPadding !== 0)
  203. scatter.yDomain(null);
  204. wrap.select('.nv-scatterWrap')
  205. .datum(data.filter(function(d) { return !d.disabled }))
  206. .call(scatter);
  207. //Adjust for x and y padding
  208. if (xPadding !== 0) {
  209. var xRange = x.domain()[1] - x.domain()[0];
  210. scatter.xDomain([x.domain()[0] - (xPadding * xRange), x.domain()[1] + (xPadding * xRange)]);
  211. }
  212. if (yPadding !== 0) {
  213. var yRange = y.domain()[1] - y.domain()[0];
  214. scatter.yDomain([y.domain()[0] - (yPadding * yRange), y.domain()[1] + (yPadding * yRange)]);
  215. }
  216. //Only need to update the scatter again if x/yPadding changed the domain.
  217. if (yPadding !== 0 || xPadding !== 0) {
  218. wrap.select('.nv-scatterWrap')
  219. .datum(data.filter(function(d) { return !d.disabled }))
  220. .call(scatter);
  221. }
  222. //------------------------------------------------------------
  223. //------------------------------------------------------------
  224. // Setup Axes
  225. if (showXAxis) {
  226. xAxis
  227. .scale(x)
  228. .ticks( xAxis.ticks() && xAxis.ticks().length ? xAxis.ticks() : availableWidth / 100 )
  229. .tickSize( -availableHeight , 0);
  230. g.select('.nv-x.nv-axis')
  231. .attr('transform', 'translate(0,' + y.range()[0] + ')')
  232. .call(xAxis);
  233. }
  234. if (showYAxis) {
  235. yAxis
  236. .scale(y)
  237. .ticks( yAxis.ticks() && yAxis.ticks().length ? yAxis.ticks() : availableHeight / 36 )
  238. .tickSize( -availableWidth, 0);
  239. g.select('.nv-y.nv-axis')
  240. .call(yAxis);
  241. }
  242. if (showDistX) {
  243. distX
  244. .getData(scatter.x())
  245. .scale(x)
  246. .width(availableWidth)
  247. .color(data.map(function(d,i) {
  248. return d.color || color(d, i);
  249. }).filter(function(d,i) { return !data[i].disabled }));
  250. gEnter.select('.nv-distWrap').append('g')
  251. .attr('class', 'nv-distributionX');
  252. g.select('.nv-distributionX')
  253. .attr('transform', 'translate(0,' + y.range()[0] + ')')
  254. .datum(data.filter(function(d) { return !d.disabled }))
  255. .call(distX);
  256. }
  257. if (showDistY) {
  258. distY
  259. .getData(scatter.y())
  260. .scale(y)
  261. .width(availableHeight)
  262. .color(data.map(function(d,i) {
  263. return d.color || color(d, i);
  264. }).filter(function(d,i) { return !data[i].disabled }));
  265. gEnter.select('.nv-distWrap').append('g')
  266. .attr('class', 'nv-distributionY');
  267. g.select('.nv-distributionY')
  268. .attr('transform',
  269. 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
  270. .datum(data.filter(function(d) { return !d.disabled }))
  271. .call(distY);
  272. }
  273. //------------------------------------------------------------
  274. if (d3.fisheye) {
  275. g.select('.nv-background')
  276. .attr('width', availableWidth)
  277. .attr('height', availableHeight);
  278. g.select('.nv-background').on('mousemove', updateFisheye);
  279. g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;});
  280. scatter.dispatch.on('elementClick.freezeFisheye', function() {
  281. pauseFisheye = !pauseFisheye;
  282. });
  283. }
  284. function updateFisheye() {
  285. if (pauseFisheye) {
  286. g.select('.nv-point-paths').style('pointer-events', 'all');
  287. return false;
  288. }
  289. g.select('.nv-point-paths').style('pointer-events', 'none' );
  290. var mouse = d3.mouse(this);
  291. x.distortion(fisheye).focus(mouse[0]);
  292. y.distortion(fisheye).focus(mouse[1]);
  293. g.select('.nv-scatterWrap')
  294. .call(scatter);
  295. if (showXAxis)
  296. g.select('.nv-x.nv-axis').call(xAxis);
  297. if (showYAxis)
  298. g.select('.nv-y.nv-axis').call(yAxis);
  299. g.select('.nv-distributionX')
  300. .datum(data.filter(function(d) { return !d.disabled }))
  301. .call(distX);
  302. g.select('.nv-distributionY')
  303. .datum(data.filter(function(d) { return !d.disabled }))
  304. .call(distY);
  305. }
  306. //============================================================
  307. // Event Handling/Dispatching (in chart's scope)
  308. //------------------------------------------------------------
  309. controls.dispatch.on('legendClick', function(d,i) {
  310. d.disabled = !d.disabled;
  311. fisheye = d.disabled ? 0 : 2.5;
  312. g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all');
  313. g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' );
  314. if (d.disabled) {
  315. x.distortion(fisheye).focus(0);
  316. y.distortion(fisheye).focus(0);
  317. g.select('.nv-scatterWrap').call(scatter);
  318. g.select('.nv-x.nv-axis').call(xAxis);
  319. g.select('.nv-y.nv-axis').call(yAxis);
  320. } else {
  321. pauseFisheye = false;
  322. }
  323. chart.update();
  324. });
  325. legend.dispatch.on('stateChange', function(newState) {
  326. state.disabled = newState.disabled;
  327. dispatch.stateChange(state);
  328. chart.update();
  329. });
  330. scatter.dispatch.on('elementMouseover.tooltip', function(e) {
  331. d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
  332. .attr('y1', function(d,i) { return e.pos[1] - availableHeight;});
  333. d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
  334. .attr('x2', e.pos[0] + distX.size());
  335. e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
  336. dispatch.tooltipShow(e);
  337. });
  338. dispatch.on('tooltipShow', function(e) {
  339. if (tooltips) showTooltip(e, that.parentNode);
  340. });
  341. // Update chart from a state object passed to event handler
  342. dispatch.on('changeState', function(e) {
  343. if (typeof e.disabled !== 'undefined') {
  344. data.forEach(function(series,i) {
  345. series.disabled = e.disabled[i];
  346. });
  347. state.disabled = e.disabled;
  348. }
  349. chart.update();
  350. });
  351. //============================================================
  352. //store old scales for use in transitions on update
  353. x0 = x.copy();
  354. y0 = y.copy();
  355. });
  356. return chart;
  357. }
  358. //============================================================
  359. // Event Handling/Dispatching (out of chart's scope)
  360. //------------------------------------------------------------
  361. scatter.dispatch.on('elementMouseout.tooltip', function(e) {
  362. dispatch.tooltipHide(e);
  363. d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
  364. .attr('y1', 0);
  365. d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
  366. .attr('x2', distY.size());
  367. });
  368. dispatch.on('tooltipHide', function() {
  369. if (tooltips) nv.tooltip.cleanup();
  370. });
  371. //============================================================
  372. //============================================================
  373. // Expose Public Variables
  374. //------------------------------------------------------------
  375. // expose chart's sub-components
  376. chart.dispatch = dispatch;
  377. chart.scatter = scatter;
  378. chart.legend = legend;
  379. chart.controls = controls;
  380. chart.xAxis = xAxis;
  381. chart.yAxis = yAxis;
  382. chart.distX = distX;
  383. chart.distY = distY;
  384. d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi');
  385. chart.options = nv.utils.optionsFunc.bind(chart);
  386. chart.margin = function(_) {
  387. if (!arguments.length) return margin;
  388. margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
  389. margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
  390. margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
  391. margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
  392. return chart;
  393. };
  394. chart.width = function(_) {
  395. if (!arguments.length) return width;
  396. width = _;
  397. return chart;
  398. };
  399. chart.height = function(_) {
  400. if (!arguments.length) return height;
  401. height = _;
  402. return chart;
  403. };
  404. chart.color = function(_) {
  405. if (!arguments.length) return color;
  406. color = nv.utils.getColor(_);
  407. legend.color(color);
  408. distX.color(color);
  409. distY.color(color);
  410. return chart;
  411. };
  412. chart.showDistX = function(_) {
  413. if (!arguments.length) return showDistX;
  414. showDistX = _;
  415. return chart;
  416. };
  417. chart.showDistY = function(_) {
  418. if (!arguments.length) return showDistY;
  419. showDistY = _;
  420. return chart;
  421. };
  422. chart.showControls = function(_) {
  423. if (!arguments.length) return showControls;
  424. showControls = _;
  425. return chart;
  426. };
  427. chart.showLegend = function(_) {
  428. if (!arguments.length) return showLegend;
  429. showLegend = _;
  430. return chart;
  431. };
  432. chart.showXAxis = function(_) {
  433. if (!arguments.length) return showXAxis;
  434. showXAxis = _;
  435. return chart;
  436. };
  437. chart.showYAxis = function(_) {
  438. if (!arguments.length) return showYAxis;
  439. showYAxis = _;
  440. return chart;
  441. };
  442. chart.rightAlignYAxis = function(_) {
  443. if(!arguments.length) return rightAlignYAxis;
  444. rightAlignYAxis = _;
  445. yAxis.orient( (_) ? 'right' : 'left');
  446. return chart;
  447. };
  448. chart.fisheye = function(_) {
  449. if (!arguments.length) return fisheye;
  450. fisheye = _;
  451. return chart;
  452. };
  453. chart.xPadding = function(_) {
  454. if (!arguments.length) return xPadding;
  455. xPadding = _;
  456. return chart;
  457. };
  458. chart.yPadding = function(_) {
  459. if (!arguments.length) return yPadding;
  460. yPadding = _;
  461. return chart;
  462. };
  463. chart.tooltips = function(_) {
  464. if (!arguments.length) return tooltips;
  465. tooltips = _;
  466. return chart;
  467. };
  468. chart.tooltipContent = function(_) {
  469. if (!arguments.length) return tooltip;
  470. tooltip = _;
  471. return chart;
  472. };
  473. chart.tooltipXContent = function(_) {
  474. if (!arguments.length) return tooltipX;
  475. tooltipX = _;
  476. return chart;
  477. };
  478. chart.tooltipYContent = function(_) {
  479. if (!arguments.length) return tooltipY;
  480. tooltipY = _;
  481. return chart;
  482. };
  483. chart.state = function(_) {
  484. if (!arguments.length) return state;
  485. state = _;
  486. return chart;
  487. };
  488. chart.defaultState = function(_) {
  489. if (!arguments.length) return defaultState;
  490. defaultState = _;
  491. return chart;
  492. };
  493. chart.noData = function(_) {
  494. if (!arguments.length) return noData;
  495. noData = _;
  496. return chart;
  497. };
  498. chart.transitionDuration = function(_) {
  499. if (!arguments.length) return transitionDuration;
  500. transitionDuration = _;
  501. return chart;
  502. };
  503. //============================================================
  504. return chart;
  505. }