Lissy93/twitter-sentiment-visualisation

View on GitHub
client-side-source/scripts/visualisations/hexagons-module.coffee

Summary

Maintainability
Test Coverage
# Hexagon color scale
scaleColors = ["#C80000", "#EB3443","#F85353","#FF5A80","#EF7B96","#B1B1B1",
  "#8CD087","#7CE974","#62EC59","#42F735","#4AF43E"]
fillScale = d3.scale.linear()
  .domain([-1,-0.8, -0.6, -0.4, -0.2, 0, 0.2, 0.4, 0.6, 0.8, 1])
  .range(scaleColors)

getSentiment = (result) ->
  if !result then return 0
  else return result.sentiment

if homePage? and homePage
  scaleColors = ['rgb(15, 160, 255)', 'rgb(60, 230, 255)', 'rgb(60, 255, 181)'
    'rgb(70, 255, 99)', 'rgb(174, 255, 99)', 'rgb(217, 255, 99)'
    'rgb(251, 255, 99)', 'rgb(255, 230, 99)', 'rgb(255, 182, 99)'
    'rgb(255, 142, 99)', 'rgb(250, 125, 100)', 'rgb(255, 117, 99)'
    'rgb(255, 99, 100)', 'rgb(255, 55, 55)'
  ]
  scaleColors.reverse()
  fillScale = d3.scale.linear()
    .domain([-1,-0.8, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1, 0, 0.1, 0.2, 0.3,
    0.4, 0.5, 0.6, 0.8, 1])
    .range(scaleColors)

points = []
svg = null
hexbin = null

renderHexChart = () ->

  # Initialise tooltip
  tip = d3.tip().attr('class', 'd3-tip').html((d, i) ->
    s = getSentiment(results[i])
    col = if s > 0 then 'green' else if s < 0 then 'darkred' else 'grey'
    html = '<b>Sentiment:</b>'
    html += ' <span style=\'color:'+col+'\'>' + s + '</span> <br>'
    html += '<span>'+results[i].body+'</span>'
    html
  )

  #svg sizes and margins
  margin = top: 30, right: 20, bottom: 20, left: 50
  if homePage? and homePage then margin = top: 0, right: 0, bottom: 0, left: 0

  width = $(window).width() - margin.left - margin.right
  height = ($(window).height() - margin.top - margin.bottom - 80)

  if !homePage? or !homePage
    width = Math.round(d3.select('#chart').node().getBoundingClientRect().width)
    height = Math.round(d3.select('#chart').node().getBoundingClientRect().height - 20)

  if hexPage? then if hexPage then height = height and width = width -= 10
  else if homePage? then if homePage then height = height * 0.8 and width += 20

  MapColumns  = Math.round(Math.sqrt(results.length*1.6))
  MapRows     =  Math.round(Math.sqrt(results.length*0.85))

  #The maximum radius the hexagons can have to still fit the screen
  hexRadius = d3.min([
    width / (MapColumns * 1.5)
    height / (MapRows * 1.2)
  ])

  #Set the new height and width of the SVG
  width = MapColumns * hexRadius * Math.sqrt(3)
  heigth = MapRows * 1.5 * hexRadius + 0.5 * hexRadius

  hexbin = d3.hexbin().radius(hexRadius)  #Set the hexagon radius

  #Calculate the center positions of each hexagon
  points = []
  i = 0
  while i < MapRows
    j = 0
    while j < MapColumns
      points.push [hexRadius * j * 1.75, hexRadius * i * 1.5]
      j++
    i++

  #Create SVG element
  svg = d3.select('#chart')
  .append('svg')
  .attr('width', width + margin.left + margin.right)
  .attr('height', height + margin.top + margin.bottom)
  .append('g')
  .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')

  svg.call(tip) # call tool tip

  #Start drawing the hexagons
  svg.append('g')
  .selectAll('.hexagon')
  .data(hexbin(points))
  .enter()
  .append('path')
  .attr('class', 'hexagon')
  .attr('d', (d) -> 'M' + d.x + ',' + d.y + hexbin.hexagon())
  .attr('stroke', (d, i) -> '#fff')
  .attr('stroke-width', '1px')
  .style('fill', (d, i) -> fillScale(getSentiment(results[i])))
  .on 'mouseover', (d,i) ->
    tip.show(d, i)
    el = d3.select(this).transition().duration(10).style('fill-opacity', 0.3)
  .on 'mouseout', (d, i) ->
    tip.hide(d, i)
    el = d3.select(this).transition().duration(1000).style('fill-opacity', 1)

# Call initial render
renderHexChart()

# Re-render the chart when the windows is resized
resizeTimer = undefined
window.addEventListener 'resize', =>
  clearTimeout resizeTimer
  resizeTimer = setTimeout((=>
    d3.select("#chart svg").remove();
    renderHexChart()
  ), 250)

# Adds a new hex
window.updateHexData = (newData) ->
  randomIndex = Math.floor(Math.random() * points.length)
  results[randomIndex] = newData
  svg.selectAll('.hexagon')
  .data(hexbin(points))
  .style('fill', (d, i) -> fillScale(getSentiment(results[i])))
  .enter()

# Socket.io
if io?
  socket = io.connect();
  socket.on 'anyTweet', (tweetObj) ->
    if tweetObj.sentiment != 0
      tweet = sentiment: tweetObj.sentiment, body: tweetObj.body
      window.updateHexData(tweet)