You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

885 lines
31 KiB

  1. nv.models.radar = function() {
  2. //============================================================
  3. // Public Variables with Default Settings
  4. //------------------------------------------------------------
  5. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  6. , width = 500
  7. , height = 500
  8. , color = nv.utils.defaultColor() // a function that returns a color
  9. , getValue = function(d) { return d.value } // accessor to get the x value from a data point
  10. , size = 5
  11. , scales = d3.scale.linear()
  12. , radius
  13. , max = 5
  14. , startAngle = 0
  15. , cursor = 0
  16. , clipEdge = false
  17. ;
  18. var line = d3.svg.line()
  19. .x(function(d) { return d.x})
  20. .y(function(d) { return d.y});
  21. var scatter = nv.models.scatter()
  22. .size(16) // default size
  23. .sizeDomain([16,256])
  24. ;
  25. //============================================================
  26. //============================================================
  27. // Private Variables
  28. //------------------------------------------------------------
  29. //============================================================
  30. function chart(selection) {
  31. selection.each(function(data) {
  32. var availableWidth = width - margin.left - margin.right,
  33. availableHeight = height - margin.top - margin.bottom,
  34. container = d3.select(this)
  35. ;
  36. // max = max || d3.max(data, getValue) > 0 ? d3.max(data, getValue) : 1
  37. scales.domain([0, max]).range([0,radius]);
  38. var current = 0;
  39. if (cursor < 0) {
  40. current = Math.abs(cursor);
  41. }
  42. else if (cursor > 0) {
  43. current = size - cursor;
  44. }
  45. //------------------------------------------------------------
  46. // Setup Scales
  47. //compute proportions
  48. var maxValue = 0;
  49. for(var i=0; i<data.length; i++) {
  50. var serie = data[i].values;
  51. for(var j=0; j<serie.length; j++) {
  52. if (serie[j].value > maxValue) {
  53. maxValue = serie[j].value;
  54. }
  55. }
  56. }
  57. var factor = maxValue ? (radius-40)/maxValue/max/2 : 0;
  58. data = data.map(function(serie, i) {
  59. serie.values = serie.values.map(function(value, j) {
  60. value.x = calculateX(value.value*factor, j, size);
  61. value.y = calculateY(value.value*factor, j, size);
  62. value.serie = i;
  63. value.focus = (current == j) ? true : false;
  64. return value;
  65. });
  66. return serie;
  67. });
  68. //------------------------------------------------------------
  69. //------------------------------------------------------------
  70. // Setup containers and skeleton of chart
  71. var wrap = container.selectAll('g.nv-wrap.nv-radar').data([data]);
  72. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-radar');
  73. var defsEnter = wrapEnter.append('defs');
  74. var gEnter = wrapEnter.append('g');
  75. var g = wrap.select('g')
  76. gEnter.append('g').attr('class', 'nv-groups');
  77. gEnter.append('g').attr('class', 'nv-scatterWrap');
  78. // wrap.attr('transform', 'translate(' + radius + ',' + radius + ')');
  79. //------------------------------------------------------------
  80. // Points
  81. scatter
  82. .xScale(scales)
  83. .yScale(scales)
  84. .zScale(scales)
  85. .color(color)
  86. .useVoronoi(false)
  87. .width(availableWidth)
  88. .height(availableHeight);
  89. var scatterWrap = wrap.select('.nv-scatterWrap');
  90. //.datum(data); // Data automatically trickles down from the wrap
  91. d3.transition(scatterWrap).call(scatter);
  92. defsEnter.append('clipPath')
  93. .attr('id', 'nv-edge-clip-' + scatter.id())
  94. .append('rect');
  95. wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
  96. .attr('width', availableWidth)
  97. .attr('height', availableHeight);
  98. g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
  99. scatterWrap
  100. .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
  101. // Series
  102. var groups = wrap.select('.nv-groups').selectAll('.nv-group').data(function(d) { return d }, function(d) { return d.key });
  103. groups.enter().append('g')
  104. .style('stroke-opacity', 1e-6)
  105. .style('fill-opacity', 1e-6);
  106. d3.transition(groups.exit())
  107. .style('stroke-opacity', 1e-6)
  108. .style('fill-opacity', 1e-6)
  109. .remove();
  110. groups
  111. .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
  112. .style('fill', function(d,i){ return color(d,i); })
  113. .style('stroke', function(d,i){ return color(d,i); });
  114. d3.transition(groups)
  115. .style('stroke-opacity', 1)
  116. .style('fill-opacity', .5);
  117. var lineRadar = groups.selectAll('path.nv-line').data(function(d) { return [d.values] });
  118. lineRadar.enter().append('path')
  119. .attr('class', 'nv-line')
  120. .attr('d', line );
  121. d3.transition(lineRadar.exit())
  122. .attr('d', line)
  123. .remove();
  124. lineRadar
  125. .style('fill', function(d){ return color(d,d[0].serie); })
  126. .style('stroke', function(d,i,j){ return color(d,d[0].serie); })
  127. d3.transition(lineRadar)
  128. .attr('d', line );
  129. });
  130. return chart;
  131. }
  132. // compute an angle
  133. function angle(i, length) {
  134. return i * (2 * Math.PI / length ) + ((2 * Math.PI) * startAngle / 360) + (cursor*2*Math.PI)/length;
  135. }
  136. // x-caclulator
  137. // d is the datapoint, i is the index, length is the length of the data
  138. function calculateX(d, i, length) {
  139. var l = scales(d);
  140. return Math.sin(angle(i, length)) * l;
  141. }
  142. // y-calculator
  143. function calculateY(d, i, length) {
  144. var l = scales(d);
  145. return Math.cos(angle(i, length)) * l;
  146. }
  147. //============================================================
  148. // Expose Public Variables
  149. //------------------------------------------------------------
  150. chart.dispatch = scatter.dispatch;
  151. chart.scatter = scatter;
  152. chart.margin = function(_) {
  153. if (!arguments.length) return margin;
  154. margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
  155. margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
  156. margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
  157. margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
  158. return chart;
  159. };
  160. chart.width = function(_) {
  161. if (!arguments.length) return width;
  162. width = _;
  163. return chart;
  164. };
  165. chart.height = function(_) {
  166. if (!arguments.length) return height;
  167. height = _;
  168. return chart;
  169. };
  170. chart.size = function(_) {
  171. if (!arguments.length) return size;
  172. size = _;
  173. return chart;
  174. };
  175. chart.scales = function(_) {
  176. if (!arguments.length) return scales;
  177. scales = _;
  178. return chart;
  179. };
  180. chart.max = function(_) {
  181. if (!arguments.length) return max;
  182. max = _;
  183. return chart;
  184. };
  185. chart.radius = function(_) {
  186. if (!arguments.length) return radius;
  187. radius = _;
  188. return chart;
  189. };
  190. chart.color = function(_) {
  191. if (!arguments.length) return color;
  192. color = nv.utils.getColor(_);
  193. return chart;
  194. };
  195. chart.startAngle = function(_) {
  196. if (!arguments.length) return startAngle;
  197. startAngle = _;
  198. return chart;
  199. };
  200. chart.cursor = function(_) {
  201. if (!arguments.length) return cursor;
  202. cursor = _;
  203. return chart;
  204. };
  205. //============================================================
  206. return chart;
  207. }
  208. nv.models.radarChart = function() {
  209. //============================================================
  210. // Public Variables with Default Settings
  211. //------------------------------------------------------------
  212. var radars = nv.models.radar()
  213. , legend = nv.models.legend();
  214. var margin = {top: 0, right: 0, bottom: 0, left: 0}
  215. , color = nv.utils.defaultColor()
  216. , width = null
  217. , height = null
  218. , showLegend = true
  219. , legs = []
  220. , ticks = 10 //Temp to test radar size issue
  221. , scales = d3.scale.linear()
  222. , edit = false
  223. , radius
  224. , startAngle = 180
  225. , cursor = 0
  226. , tooltips = true
  227. , transitionDuration = 250
  228. , tooltip = function(key, leg, value, e, graph) {
  229. return '<h3>' + key + '</h3>' +
  230. '<p>' + leg + ': ' + value + '</p>'
  231. }
  232. , dispatch = d3.dispatch('tooltipShow', 'tooltipHide','prevClick','stateChange')
  233. ;
  234. var line = d3.svg.line()
  235. .x(function(d) { return d.x})
  236. .y(function(d) { return d.y});
  237. //============================================================
  238. //============================================================
  239. // Private Variables
  240. //------------------------------------------------------------
  241. var showTooltip = function(e, offsetElement) {
  242. // New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else
  243. if (offsetElement) {
  244. var svg = d3.select(offsetElement).select('svg');
  245. var viewBox = svg.attr('viewBox');
  246. if (viewBox) {
  247. viewBox = viewBox.split(' ');
  248. var ratio = parseInt(svg.style('width')) / viewBox[2];
  249. e.pos[0] = e.pos[0] * ratio;
  250. e.pos[1] = e.pos[1] * ratio;
  251. }
  252. }
  253. var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
  254. top = e.pos[1] + ( offsetElement.offsetTop || 0),
  255. val = e.series.values[e.pointIndex].value,
  256. leg = legs[e.pointIndex].label,
  257. content = tooltip(e.series.key, leg, val, e, chart);
  258. nv.tooltip.show([left, top], content, null, null, offsetElement);
  259. };
  260. //============================================================
  261. function chart(selection) {
  262. selection.each(function(data) {
  263. legs=data[0].values;//TODO: Think in a better way to put only the legs of the radar
  264. var container = d3.select(this),
  265. that = this,
  266. size = legs.length,
  267. availableWidth = (width || parseInt(container.style('width')) || 500) - margin.left - margin.right,
  268. availableHeight = (height || parseInt(container.style('height')) || 500) - margin.top - margin.bottom;
  269. chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
  270. chart.container = this;
  271. var current = 0;
  272. if (cursor < 0) {
  273. current = Math.abs(cursor);
  274. }
  275. else if (cursor > 0) {
  276. current = legs.length - cursor;
  277. }
  278. //------------------------------------------------------------
  279. // Setup Scales
  280. // scales = radars.scales();
  281. radius = (availableWidth-300 >= availableHeight) ? (availableHeight)/2 : (availableWidth-300)/2;
  282. scales.domain([0, ticks]).range([0,radius]);
  283. //------------------------------------------------------------
  284. //------------------------------------------------------------
  285. // Setup containers and skeleton of chart
  286. var wrap = container.selectAll('g.nv-wrap.nv-radarChart').data([data]);
  287. var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-radarChart');
  288. var gEnter = wrapEnter.append('g');
  289. var g = wrap.select('g');
  290. gEnter.append('g').attr('class', 'nv-controlWrap');
  291. gEnter.append('g').attr('class', 'nv-gridWrap');
  292. gEnter.append('g').attr('class', 'nv-radarsWrap');
  293. gEnter.append('g').attr('class', 'nv-legendWrap');
  294. var gridWrap = wrap.select('g.nv-gridWrap');
  295. gridWrap.append("g").attr("class", "grid");
  296. gridWrap.append("g").attr("class", "axes");
  297. wrap.attr('transform', 'translate(' + parseFloat(radius + margin.left) + ',' + parseFloat(radius + margin.top) + ')');
  298. //------------------------------------------------------------
  299. //------------------------------------------------------------
  300. // Legend
  301. if (showLegend) {
  302. legend.width(30);
  303. g.select('.nv-legendWrap')
  304. .datum(data)
  305. .call(legend);
  306. /*
  307. if ( margin.top != legend.height()) {
  308. margin.top = legend.height();
  309. availableHeight = (height || parseInt(container.style('height')) || 400)
  310. - margin.top - margin.bottom;
  311. }
  312. */
  313. g.select('.nv-legendWrap')
  314. .attr('transform', 'translate(' + (radius + margin.left + margin.right) + ',' + (-radius) +')');
  315. }
  316. //------------------------------------------------------------
  317. if (edit) {
  318. startAngle = 135
  319. //Focus
  320. var currentLeg = legs[current];
  321. var rgbLeg = hexToRgb("#000000");
  322. var controlWrap = wrap.select('g.nv-controlWrap');
  323. wrap.select('g.control').remove();
  324. var controlEnter = controlWrap.append("g")
  325. .attr("class", "control");
  326. var controlLine = controlEnter.append("svg:line")
  327. .attr('class', 'indicator')
  328. .style("stroke", "#000000")
  329. .style("fill", "none")
  330. .style("opacity", 1)
  331. .style("stroke-width", 1.5)
  332. .attr("x1", Math.sin(angle(current, size)) * scales(scales.domain()[1]))
  333. .attr("y1", Math.cos(angle(current, size)) * scales(scales.domain()[1]))
  334. .attr("x2", Math.sin(angle(current, size)) * scales(scales.domain()[1]))
  335. .attr("y2", Math.cos(angle(current, size)) * scales(scales.domain()[1]));
  336. var controlDescription = controlEnter.append("svg:foreignObject")
  337. .attr('width',200)
  338. .attr('height',0)
  339. .attr("x", Math.sin(angle(current, size)) * scales(scales.domain()[1]) * 2)
  340. .attr("y", Math.cos(angle(current, size)) * scales(scales.domain()[1]));
  341. controlDescription.append("xhtml:div")
  342. .attr('class', 'radar-description')
  343. .style("background-color", 'rgba('+rgbLeg.r+','+rgbLeg.g+','+rgbLeg.b+',0.1)')
  344. .style('border-bottom', '1px solid '+"#000000")
  345. .style("padding", "10px")
  346. .style("text-align", "justify")
  347. .text( currentLeg.description );
  348. var controlActionContent = controlEnter.append("svg:foreignObject")
  349. .attr('width',200)
  350. .attr('height',50)
  351. .attr("x", Math.sin(angle(current, size)) * scales(scales.domain()[1]) * 2)
  352. .attr("y", Math.cos(angle(current, size)) * scales(scales.domain()[1]) - 25);
  353. controlActionContent.append("xhtml:button")
  354. .attr('type','button')
  355. .attr('class','radar-prev btn btn-mini icon-arrow-left')
  356. .text('prev');
  357. var controlSelect = controlActionContent.append("xhtml:select")
  358. .attr('class','radar-select-note');
  359. controlSelect.append('xhtml:option')
  360. .attr('value',0)
  361. // .attr('selected', function(d,i){ return (d[0].values[current].value == 0) ? true : false;})
  362. .text('Note')
  363. controlSelect.append('xhtml:option')
  364. .attr('value',1)
  365. // .attr('selected', function(d,i){ return (d[0].values[current].value == 1) ? true : false;})
  366. .text('Nul')
  367. controlSelect.append('xhtml:option')
  368. .attr('value',2)
  369. //.attr('selected', function(d,i){ return (d[0].values[current].value == 2) ? true : false;})
  370. .text('Mauvais')
  371. controlSelect.append('xhtml:option')
  372. .attr('value',3)
  373. // .attr('selected', function(d,i){ return (d[0].values[current].value == 3) ? true : false;})
  374. .text('Nul')
  375. controlSelect.append('xhtml:option')
  376. .attr('value',4)
  377. // .attr('selected', function(d,i){ return (d[0].values[current].value == 4) ? true : false;})
  378. .text('Bien')
  379. controlSelect.append('xhtml:option')
  380. .attr('value',5)
  381. // .attr('selected', function(d,i){ return (d[0].values[current].value == 4) ? true : false;})
  382. .text('Très bien')
  383. controlActionContent.append("xhtml:button")
  384. .attr('type','button')
  385. .attr('class','radar-next btn btn-mini icon-arrow-right')
  386. .text('next');
  387. var checkOption = function (d) {
  388. if(d[0].values[current].value == this.value){
  389. return d3.select(this).attr("selected", "selected");
  390. }
  391. };
  392. controlSelect.selectAll("option").each(checkOption);
  393. // Animation
  394. controlLine.transition().duration(500)
  395. .attr("x1", Math.sin(angle(current, size)) * scales(scales.domain()[1]))
  396. .attr("y1", Math.cos(angle(current, size)) * scales(scales.domain()[1]))
  397. .attr("x2", Math.sin(angle(current, size)) * scales(scales.domain()[1]) * 2 + 200)
  398. .attr("y2", Math.cos(angle(current, size)) * scales(scales.domain()[1]))
  399. .each('end', function(d){ controlDescription.transition().duration(300).attr('height','100%') });
  400. // Controls
  401. controlWrap.select('.radar-prev')
  402. .on('click', function(d) {
  403. chart.prev();
  404. selection.transition().call(chart);
  405. });
  406. controlWrap.select('.radar-next')
  407. .on('click', function(d) {
  408. chart.next();
  409. selection.transition().call(chart);
  410. });
  411. controlWrap.select('.radar-select-note')
  412. .on('change', function(d) {
  413. d[0].values[current].value = this.value;
  414. chart.next();
  415. selection.transition().call(chart);
  416. });
  417. //change
  418. } else {
  419. cursor = 0;
  420. startAngle = 180;
  421. wrap.select('g.control').remove();
  422. }
  423. //------------------------------------------------------------
  424. // Main Chart Component(s)
  425. radars
  426. .width(availableWidth)
  427. .height(availableHeight)
  428. .size(legs.length)
  429. .max(ticks)
  430. .startAngle(startAngle)
  431. .cursor(cursor)
  432. // .scales(scales)
  433. .radius(radius)
  434. .color(data.map(function(d,i) {
  435. return d.color || color(d, i);
  436. }).filter(function(d,i) { return !data[i].disabled }))
  437. ;
  438. var radarWrap = g.select('.nv-radarsWrap')
  439. .datum(data.filter(function(d) { return !d.disabled }));
  440. d3.transition(radarWrap).call(radars);
  441. //------------------------------------------------------------
  442. //------------------------------------------------------------
  443. // Setup Axes
  444. // the grid data, number of ticks
  445. var gridData = buildAxisGrid(size, ticks);
  446. // Grid
  447. var grid = wrap.select('.grid').selectAll('.gridlevel').data(gridData);
  448. grid.exit().remove();
  449. grid.enter().append("path")
  450. .attr("class", "gridlevel")
  451. .attr("d", line);
  452. d3.transition(grid)
  453. .attr('d', line );
  454. grid.style("stroke", "#000")
  455. .style("fill", "none")
  456. .style("opacity", 0.3);
  457. // Axes
  458. var ax = wrap.select("g.axes").selectAll("g.axis").data(legs);
  459. ax.exit().remove();
  460. var axEnter = ax.enter().append("g")
  461. .attr("class", "axis");
  462. var legText = axEnter.append("svg:text")
  463. .style("text-anchor", function(d, i) {
  464. var x = Math.sin(angle(i, size)) * scales(scales.domain()[1]);
  465. if (Math.abs(x) < 0.1) {
  466. return "middle"
  467. }
  468. if (x > 0) {
  469. return "start"
  470. }
  471. return "end"
  472. })
  473. .attr("dy", function(d, i) {
  474. var y = Math.cos(angle(i, size)) * scales(scales.domain()[1]);
  475. if (Math.abs(y) < 0.1) {
  476. return ".72em"
  477. }
  478. if (y > 0) {
  479. return "1em"
  480. }
  481. return "-.3em"
  482. })
  483. .style("fill", function(d){ return d.color; })
  484. .style("font-size", "9pt")
  485. .style("font-weight",function(d,i){ return (i == current && edit) ? "bold": "normal"; })
  486. .style("opacity", function(d,i){ return (i == current && edit) ? 1: 0.4; })
  487. .text(function(d){ return d.label})
  488. .attr("x", function(d, i) { return Math.sin(angle(i, size)) * scales(scales.domain()[1]);})
  489. .attr("y", function(d, i) { return Math.cos(angle(i, size)) * scales(scales.domain()[1]);})
  490. ;
  491. legText.on('click', function(d,i) {
  492. chart.cursor(legs.length - i);
  493. selection.transition().call(chart);
  494. });
  495. d3.transition(ax)
  496. .select("text")
  497. .style("text-anchor", function(d, i) {
  498. var x = Math.sin(angle(i, size)) * scales(scales.domain()[1]);
  499. if (Math.abs(x) < 0.1) {
  500. return "middle"
  501. }
  502. if (x > 0) {
  503. return "start"
  504. }
  505. return "end"
  506. })
  507. .attr("dy", function(d, i) {
  508. var y = Math.cos(angle(i, size)) * scales(scales.domain()[1]);
  509. if (Math.abs(y) < 0.1) {
  510. return ".72em"
  511. }
  512. if (y > 0) {
  513. return "1em"
  514. }
  515. return "-.3em"
  516. })
  517. .style("font-weight",function(d,i){ return (i == current && edit) ? "bold": "normal"; })
  518. .style("opacity", function(d,i){ return (i == current && edit) ? 1: 0.4; })
  519. .attr("x", function(d, i) { return Math.sin(angle(i, size)) * scales(scales.domain()[1]);})
  520. .attr("y", function(d, i) { return Math.cos(angle(i, size)) * scales(scales.domain()[1]);});
  521. axEnter.append("svg:line")
  522. .style("stroke", function(d){ return d.color; })
  523. .style("fill", "none")
  524. .style("stroke-width", 2)
  525. .style("opacity", function(d,i){ return (i == current && edit) ? 1: 0.4; })
  526. .attr("x1", function(d, i) { return Math.sin(angle(i, size)) * scales(scales.domain()[0]);})
  527. .attr("y1", function(d, i) { return Math.cos(angle(i, size)) * scales(scales.domain()[0]);})
  528. .attr("x2", function(d, i) { return Math.sin(angle(i, size)) * scales(scales.domain()[1]);})
  529. .attr("y2", function(d, i) { return Math.cos(angle(i, size)) * scales(scales.domain()[1]);});
  530. d3.transition(ax)
  531. .select("line")
  532. .style("opacity", function(d,i){ return (i == current && edit) ? 1: 0.4; })
  533. .attr("x1", function(d, i) { return Math.sin(angle(i, size)) * scales(scales.domain()[0]);})
  534. .attr("y1", function(d, i) { return Math.cos(angle(i, size)) * scales(scales.domain()[0]);})
  535. .attr("x2", function(d, i) { return Math.sin(angle(i, size)) * scales(scales.domain()[1]);})
  536. .attr("y2", function(d, i) { return Math.cos(angle(i, size)) * scales(scales.domain()[1]);});
  537. //------------------------------------------------------------
  538. //============================================================
  539. // Event Handling/Dispatching (in chart's scope)
  540. //------------------------------------------------------------
  541. radars.dispatch.on('elementClick', function(d,i) {
  542. chart.cursor(legs.length - d.pointIndex);
  543. selection.transition().call(chart);
  544. });
  545. legend.dispatch.on('stateChange', function(newState) {
  546. state = newState;
  547. dispatch.stateChange(state);
  548. chart.update();
  549. });
  550. /*legend.dispatch.on('legendClick', function(d,i) {
  551. if (!d.disabled) return;
  552. data = data.map(function(s) {
  553. s.disabled = true;
  554. return s;
  555. });
  556. d.disabled = false;
  557. switch (d.key) {
  558. case 'Grouped':
  559. multibar.stacked(false);
  560. break;
  561. case 'Stacked':
  562. multibar.stacked(true);
  563. break;
  564. }
  565. state.stacked = multibar.stacked();
  566. dispatch.stateChange(state);
  567. chart.update();
  568. });*/
  569. /* legend.dispatch.on('legendClick', function(d,i) {
  570. d.disabled = !d.disabled;
  571. if (!data.filter(function(d) { return !d.disabled }).length) {
  572. data.map(function(d) {
  573. d.disabled = false;
  574. wrap.selectAll('.nv-series').classed('disabled', false);
  575. return d;
  576. });
  577. }
  578. chart.update();
  579. });*/
  580. dispatch.on('tooltipShow', function(e) {
  581. e.pos = [parseFloat(e.pos[0] + availableHeight/2 + margin.left), parseFloat(e.pos[1] + availableHeight/2 + margin.top)];
  582. if (tooltips) showTooltip(e, that.parentNode);
  583. });
  584. //============================================================
  585. });
  586. return chart;
  587. }
  588. function hexToRgb(hex,opacity) {
  589. var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  590. return result ? {
  591. r: parseInt(result[1], 16),
  592. g: parseInt(result[2], 16),
  593. b: parseInt(result[3], 16)
  594. } : null;
  595. }
  596. function rgbToHex(r, g, b) {
  597. return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
  598. }
  599. // compute an angle
  600. function angle(i, length) {
  601. return i * (2 * Math.PI / length ) + ((2 * Math.PI) * startAngle / 360) + (cursor*2*Math.PI)/length;
  602. }
  603. // x-caclulator
  604. // d is the datapoint, i is the index, length is the length of the data
  605. function calculateX(d, i, length) {
  606. var l = scales(d);
  607. return Math.sin(angle(i, length)) * l;
  608. }
  609. // y-calculator
  610. function calculateY(d, i, length) {
  611. var l = scales(d);
  612. return Math.cos(angle(i, length)) * l;
  613. }
  614. // * build the spider axis * //
  615. // rewrite this to conform to d3 axis style? //
  616. function buildAxisGrid(length, ticks) {
  617. var min = scales.domain()[0];
  618. var max = scales.domain()[1] > 0 ? scales.domain()[1] : 1;
  619. var increase = max/ticks;
  620. var gridData = []
  621. for (var i = 0; i <= ticks; i++ ) {
  622. var val = min + i*increase;
  623. var d = [val];
  624. var gridPoints = [];
  625. for (var j = 0; j <= length; j++) {
  626. gridPoints.push({
  627. x: calculateX(d, j, length),
  628. y: calculateY(d, j, length),
  629. });
  630. }
  631. gridData.push(gridPoints)
  632. }
  633. return gridData;
  634. }
  635. //============================================================
  636. // Event Handling/Dispatching (out of chart's scope)
  637. //------------------------------------------------------------
  638. radars.dispatch.on('elementMouseover.tooltip', function(e) {
  639. dispatch.tooltipShow(e);
  640. });
  641. radars.dispatch.on('elementMouseout.tooltip', function(e) {
  642. dispatch.tooltipHide(e);
  643. });
  644. dispatch.on('tooltipHide', function() {
  645. if (tooltips) nv.tooltip.cleanup();
  646. });
  647. //============================================================
  648. //============================================================
  649. // Expose Public Variables
  650. //------------------------------------------------------------
  651. // expose chart's sub-components
  652. chart.dispatch = dispatch;
  653. chart.radars = radars;
  654. chart.margin = function(_) {
  655. if (!arguments.length) return margin;
  656. margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
  657. margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
  658. margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
  659. margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
  660. return chart;
  661. };
  662. chart.width = function(_) {
  663. if (!arguments.length) return width;
  664. width = _;
  665. return chart;
  666. };
  667. chart.height = function(_) {
  668. if (!arguments.length) return height;
  669. height = _;
  670. return chart;
  671. };
  672. chart.legs = function(_) {
  673. if (!arguments.length) return legs;
  674. legs = _;
  675. return chart;
  676. };
  677. chart.showLegend = function(_) {
  678. if (!arguments.length) return showLegend;
  679. showLegend = _;
  680. return chart;
  681. };
  682. chart.cursor = function(_) {
  683. if (!arguments.length) return cursor;
  684. cursor = _;
  685. return chart;
  686. };
  687. chart.next = function(_) {
  688. cursor = cursor - 1;
  689. if (Math.abs(cursor) > legs.length-1) cursor = 0;
  690. return chart;
  691. };
  692. chart.prev = function(_) {
  693. cursor = cursor + 1;
  694. if (cursor > legs.length-1) cursor = 0;
  695. return chart;
  696. };
  697. chart.edit = function(_) {
  698. if (!arguments.length) return edit;
  699. edit = _;
  700. return chart;
  701. };
  702. //============================================================
  703. return chart;
  704. }