poll-generator.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import * as d3 from 'd3';
  2. import { repr } from '../utilities/formatter';
  3. // d3js module for module04: poll generator
  4. export default () => {
  5. // defaults
  6. const options = {
  7. width: 475,
  8. height: 500,
  9. radius: 10,
  10. data: [],
  11. pollindex: 0,
  12. setData: () => {} // eslint-disable-line no-empty-function
  13. };
  14. // start
  15. const poll = selection => {
  16. const svg = selection.select('svg');
  17. const scores = options.data.map(distribution => distribution.score);
  18. const xScale = d3.scaleLinear().domain([ 0, 100 ]).range([ 0, options.width ]);
  19. const bisect = d3.bisector(d => d.cumul).left;
  20. // reset everything
  21. const reset = () => {
  22. svg.select('.counter').text('');
  23. svg.selectAll('rect.diff, rect.election').transition().duration(1000).attr('width', 0);
  24. svg.selectAll('text.difftext, text.result').text('');
  25. svg.selectAll('rect.poll').transition().duration(1000).attr('width', 0);
  26. svg.selectAll('text.polltext').text('');
  27. svg.selectAll('circle').transition()
  28. .duration(2000)
  29. .attr('cy', () => (options.height * Math.random()))
  30. .attr('cx', () => (options.width * Math.random()))
  31. .attr('fill-opacity', 0.2);
  32. };
  33. // generate new poll
  34. const generatepoll = pollind => {
  35. if (options.pollindex === 0) reset();
  36. let xIndex;
  37. let yind;
  38. // determine correct position
  39. if (pollind < options.numberOfCols) {
  40. xIndex = pollind;
  41. yind = 0;
  42. } else {
  43. xIndex = pollind - options.numberOfCols;
  44. yind = 1;
  45. }
  46. const polldata = [ 0, 0, 0, 0, 0 ];
  47. let pollperc = [ 0, 0, 0, 0, 0 ];
  48. let pollcounter = 0;
  49. // add them one by one
  50. const anim = setInterval(() => {
  51. animatepoll(1000); // eslint-disable-line no-use-before-define
  52. }, 1);
  53. // show poll results
  54. const animatepoll = pollsize => {
  55. if (pollcounter === pollsize) {
  56. clearInterval(anim);
  57. if (options.pollindex === 4) {
  58. options.setData({ continue: true });
  59. }
  60. }
  61. pollcounter += 1;
  62. // draw random sample ('index' corresponds to class index in 'data.json')
  63. const index = bisect(options.data, Math.random() * 100);
  64. // add to polldata
  65. polldata[index] += 1;
  66. // calculate difference with result
  67. pollperc = polldata.map(score => (Math.round(score / pollcounter * 1000) / 10));
  68. pollperc.forEach((element, i) => {
  69. pollperc[i] = Math.round((pollperc[i] - scores[i]) * 100) / 100;
  70. });
  71. const distance = (options.width - options.xoffset * 2) / (options.numberOfCols - 1);
  72. const xBase = options.xoffset + xIndex * distance;
  73. // increase rectangle width
  74. svg.selectAll(`rect.diff${xIndex}${yind}`)
  75. .data(pollperc)
  76. .attr('width', d => (xScale(Math.abs(d))))
  77. .attr('x', d => (d < 0 ? xBase - Math.abs(xScale(d)) : xBase));
  78. // add text, format it
  79. svg.selectAll(`.difftext${xIndex}${yind}`)
  80. .data(pollperc)
  81. .attr('x', d => (d > 0 ? xBase + xScale(d) + 7 : xBase + xScale(d) - 6))
  82. .attr('text-anchor', d => (d > 0 ? 'start' : 'end'))
  83. .text(d => repr(d))
  84. .attr('fill-opacity', pollcounter > 100 ? 1 : 0);
  85. };
  86. };
  87. // start next poll
  88. generatepoll(options.pollindex);
  89. };
  90. // "setter"
  91. poll.options = input => {
  92. Object.assign(options, input);
  93. return poll;
  94. };
  95. return poll;
  96. };