Source: view/viz/VizSampleDensity2D.js

  1. /** @module */
  2. import { range } from '@paretoman/votekit-utilities'
  3. import { contourDensity } from 'd3-contour'
  4. import { geoPath } from 'd3-geo'
  5. import VoterRender1D from './VoterRender1D.js'
  6. import VoterRender2D from './VoterRender2D.js'
  7. /**
  8. * Show Voters
  9. * @param {VoterRendererList} voterRendererList
  10. * @param {screen} screen - draw to the screen
  11. * @constructor
  12. */
  13. // eslint-disable-next-line max-len
  14. export default function VizSampleDensity2D(voterRendererList, canDnRendererList, screen, changes, simOptions) {
  15. const self = this
  16. // Candidates //
  17. const { dimensions } = simOptions
  18. // voter renderer factory //
  19. const VoterRenderer = (dimensions === 1) ? VoterRender1D : VoterRender2D
  20. voterRendererList.setRenderer((voterShape) => new VoterRenderer(voterShape, screen))
  21. canDnRendererList.setRenderer((voterShape) => new VoterRenderer(voterShape, screen))
  22. const pointsList = []
  23. const pointCounts = []
  24. let totalCount
  25. self.update = function (samplingResult) {
  26. if (changes.checkAny()) {
  27. start()
  28. }
  29. const { pointsChanged, samplingPointsList, samplingPointsCount } = samplingResult
  30. if (pointsChanged) {
  31. self.updatePoints(samplingPointsList, samplingPointsCount)
  32. self.updateDensity()
  33. }
  34. }
  35. function start() {
  36. pointsList.splice(0, pointsList.length)
  37. pointCounts.splice(0, pointCounts.length)
  38. totalCount = 0
  39. }
  40. self.render = () => {
  41. self.renderCans()
  42. voterRendererList.render()
  43. canDnRendererList.render()
  44. }
  45. self.updatePoints = function (samplingPointsList, samplingPointsCount) {
  46. for (let i = 0; i < samplingPointsList.length; i++) {
  47. const point = samplingPointsList[i]
  48. const count = samplingPointsCount[i]
  49. pointsList.push(point)
  50. pointCounts.push(count)
  51. totalCount += count
  52. }
  53. }
  54. const nThresholds = 20
  55. let densityData
  56. self.updateDensity = () => {
  57. const btw = totalCount / 10000
  58. const thresholds = range(nThresholds).map((x) => x * btw)
  59. const cd = contourDensity()
  60. .x((d) => d[0])
  61. .y((d) => d[1])
  62. .weight((d, i) => pointCounts[i])
  63. .bandwidth(10)
  64. .thresholds(thresholds)
  65. densityData = cd(pointsList)
  66. }
  67. self.renderCans = () => {
  68. const { ctx } = screen
  69. const { darkMode } = screen.common
  70. const gg = geoPath()
  71. .context(ctx)
  72. ctx.save()
  73. ctx.lineWidth = 0.5
  74. ctx.strokeStyle = '#aaa'
  75. const alpha = 1
  76. const nd = densityData.length
  77. for (let i = 0; i < nd; i++) {
  78. const col = 255 - i * (200 / nThresholds)
  79. // 33 is #222, otherwise there is noise TODO: fix this workaround. Try to take out 33.
  80. const co = (darkMode) ? 255 - col + 33 : col
  81. ctx.fillStyle = `rgba(${co},${co},${co}, ${alpha})`
  82. ctx.beginPath()
  83. gg(densityData[i])
  84. // ctx.stroke()
  85. ctx.fill()
  86. }
  87. ctx.restore()
  88. }
  89. }