Lissy93/twitter-sentiment-visualisation

View on GitHub
client-side-source/scripts/visualisations/scatter-words-main.coffee

Summary

Maintainability
Test Coverage

d3 = require 'd3/d3.min.js'
tipsy = require 'tipsy/index.js'

mainPage = 'word-scatter-plot'
pageControls = require '../page-controls-module.coffee'
pageControls.setMainPage mainPage


# set the stage
margin = {t: 30, r: 20, b: 20, l: 40}
w = 600 - (margin.l) - (margin.r)
h = 500 - (margin.t) - (margin.b)


biggestFreq = 10
for e, i in wordData
  if e.freq > biggestFreq then biggestFreq = e.freq
  ran = Math.round(Math.random()*10)/100
  wordData[i].sentiment = wordData[i].sentiment + ran


x = d3.scale.linear().range([0, w/4, w/2, (w/4)*3, w])
y = d3.scale.linear().range([h - 60, 0])

scaleColors = ["#a50026","#d73027","#fdae61" ,"#B4B4B4",
               "#a6d96a","#1a9850","#006837"]

color = d3.scale.linear()
  .domain([-0.6, -0.4, -0.2, 0, 0.2, 0.4, 0.6])
  .range(scaleColors)



svg = d3.select('#scatter-words')
  .append('svg')
  .attr('width', w + margin.l + margin.r)
  .attr('height', h + margin.t + margin.b)

# set axes, as well as details on their ticks
xAxis = d3.svg.axis().scale(x)
  .ticks(20).tickSubdivide(true).tickSize(6, 3, 0).orient('bottom')
yAxis = d3.svg.axis().scale(y)
  .ticks(20).tickSubdivide(true).tickSize(6, 3, 0).orient('left')

# group that will contain all of the plots
groups = svg.append('g')
  .attr('transform', 'translate(' + margin.l + ',' + margin.t + ')')

# array of the sentiments, used for the legend
sentiments = ['Positive', 'Neutral', 'Negative' ]

x0 = Math.max(-d3.min(wordData, (d) -> d.freq
), d3.max(wordData, (d) -> d.freq )
)
x.domain [0, 10, 20, 50, biggestFreq]
y.domain [-0.6, 0.6]

# style the circles, set their locations based on data
circles = groups.selectAll('circle')
  .data(wordData)
  .enter()
  .append('circle')
  .attr('class', 'circles')
  .attr(
    cx: (d) -> x +d.freq
    cy: (d) -> y +d.sentiment
    r: 8
    id: (d) -> d.text
  )
  .style('fill', (d) -> color d.sentiment )
  .style('opacity','0.6')


# what to do when we mouse over a bubble
mouseOn = ->
  circle = d3.select(this)
  # transition to increase size/opacity of bubble
  circle.transition()
    .duration(800)
    .style('opacity', 1)
    .attr('r', 16).ease 'elastic'

  # append lines to bubbles that will be used to show the precise data points.
  # translate their location based on margins
  svg.append('g')
    .attr('class', 'guide')
    .append('line')
    .attr('x1', circle.attr('cx'))
    .attr('x2', circle.attr('cx'))
    .attr('y1', +circle.attr('cy') + 26)
    .attr('y2', h - (margin.t) - (margin.b))
    .attr('transform', 'translate(40,20)')
    .style('stroke', circle.style('fill'))
    .transition()
    .delay(200)
    .duration(400)
    .styleTween 'opacity', -> d3.interpolate 0, .5

  svg.append('g')
    .attr('class', 'guide')
    .append('line')
    .attr('x1', +circle.attr('cx') - 16)
    .attr('x2', 0)
    .attr('y1', circle.attr('cy'))
    .attr('y2', circle.attr('cy'))
    .attr('transform', 'translate(40,30)')
    .style('stroke', circle.style('fill'))
    .transition().delay(200).duration(400)
    .styleTween 'opacity', -> d3.interpolate 0, .5

  # function to move mouseover item to front of SVG stage, in case
  # another bubble overlaps it
  d3.selection::moveToFront = ->
    @each ->
      @parentNode.appendChild this


  circle.moveToFront()

# what happens when we leave a bubble?
mouseOff = ->
  circle = d3.select(this)

  # go back to original size and opacity
  circle.transition()
    .duration(800)
    .style('opacity', .4)
    .attr('r', 8)
    .ease 'elastic'

  # fade out guide lines, then remove them
  d3.selectAll('.guide').transition().duration(100).styleTween('opacity', ->
    d3.interpolate .5, 0
  ).remove()


# run the mouseon/out functions
circles.on 'mouseover', mouseOn
circles.on 'mouseout', mouseOff


# tooltips (using jQuery plugin tipsy)
circles.append('title')
  .text (d) -> d.text

$('.circles').tipsy gravity: 's'


circles
  .attr('class', 'tooltipped')
  .attr('data-position', 'bottom')
  .attr('data-delay', '50')
  .attr('data-tooltip', (d) -> d.text)


# the legend color guide
legend = svg.selectAll('rect')
  .data(sentiments)
  .enter()
  .append('rect')
  .attr(
    x: (d, i) -> 40 + i * 80
    y: h
    width: 25
    height: 12)
  .style('fill', (d) ->
    if d == 'Positive' then return color 0.5
    if d == 'Negative' then return color -0.5
    else return color 0
  )


# legend labels
svg.selectAll('text').data(sentiments).enter().append('text').attr(
  x: (d, i) -> 40 + i * 80
  y: h + 24).text (d) -> d


# draw axes and axis labels
svg.append('g')
  .attr('class', 'x axis')
  .attr('transform', "translate(#{margin.l}, #{(h - 60 + margin.t)})")
  .call xAxis

svg.append('g')
  .attr('class', 'y axis')
  .attr('transform', "translate(#{margin.l},#{margin.t})")
  .call yAxis

svg.append('text')
  .attr('class', 'x label')
  .attr('text-anchor', 'end')
  .attr('x', w + 50)
  .attr('y', h - (margin.t) - 5)
  .text 'Frequency'

svg.append('text')
  .attr('class', 'y label')
  .attr('text-anchor', 'end')
  .attr('x', -20)
  .attr('y', 45)
  .attr('dy', '.75em')
  .attr('transform', 'rotate(-90)')
  .text 'Sentiment'


$(document).ready ->
  $('.tooltipped').tooltip delay: 50