Visualizing Emotions
By: Nivedhitha Mathan Kumar
Tags: Sentiment Analysis, Emotions, Text, NLP, Data Visualization

Visualizing Emotions

I wanted to display the emotions of a text as we read it through.

I started by exploring the lexical database, WordNet. My initial idea was to create a graph of all the words (excluding stop-words) connected to their synonyms using WordNet and track the words as it appears on the text. I expected them to create clusters of related words.

Visualized with Gephi (find the shortest path)

But oops! They were haphazard; I had missed something important. Say, A and B share synsets, and so do B and C. And yet, A and C might have no relation in meaning. For example, a common node connects ‘take’ and ‘occupy’. Another node connects ‘concern’ and ‘occupy’. This links ‘take’ and ‘concern’ even though they do not carry similar meanings.

The next time, I tried, again using WordNet, to form a tree connected through hypernyms. I tried extending as many leaf nodes as possible to the root ‘entity’ using derivationally related forms, similar-tos, and verb groups. I used Mike Bostock’s Radial Dendrogram to display the data.

Visualized with Mike Bostock’s Radial Dendrogram

This time, though it formed clusters, the words were not clustered based on meaning.

For example, the feelings cluster is placed away from, say, the physical entity cluster. However, within the feelings cluster, the nodes ‘happy’ and ‘angered’ are placed close-by. While the two nodes are closely related by hypernyms, they are opposite in meaning.

FINALLY!

Finally, I settled upon the NRC Emotion Intensity Lexicon, aka Affect Intensity Lexicon by Saif M. Mohammad. It contains words scored in the range of 0 to 1 for each of the four emotions: anger, fear, joy, and sadness. I considered 3 of the emotions and used their scores and a color wheel to find their positions.

var xyArray=[];

function data2rgb(filename)

{

let D=2*R;

let rgbArray=[];

let scaleColor= d3.scaleLinear().domain([0,1]).range([0,255]);

d3.csv(filename).then(function(data)

{

for(let i=0;i<data.length;i++)

{

let [red,green,blue]=

[scaleColor(data[i]["anger"]),

scaleColor(data[i]["joy"]),

scaleColor(data[i]["fear"])];



let xyval=rgb2xy(red,green,blue,data[i]["negate"]);

xyArray.push({i:i, word:data[i].word, x:xyval.x,

y:xyval.y, h:xyval.h, s:xyval.s, v:xyval.v});

}

intervalLoop(xyArray);

});

}

To begin with, I took the help of the article Hand-coding a color wheel with canvas by Cory Forsyth. I started with collecting the words of a text that could be found in the lexicon, and their scores. I then scaled each score to the range of 0 to 255 and assigned it a color (green for joy, red for anger, and blue for fear). After converting the RGB value to HSV, I could get its position in a circle from H and S, and value from V.

function repeating()

{

timeouts=setTimeout(function()

{ if(i==xyArray.length-1)

return 0;

drawNode(xyArray[i],xyArray[i+1]);

i++;

repeating();

},tt); function drawNode(a,b)

{...}

}

repeating();

Then, using the setTimeout() method, I evaluated the function to draw the SVG elements regularly after an interval. During this interval, with the d3 transition, I could linearly translate between the nodes and transit the color of nodes. But I wanted to create a smoother path; not a linear one.

So, I created a Catmull-Rom spline using d3 of no stroke or fill and broke it into segments. I then added the data of points along the broken segment to that of each word to use within the setTimeout()’s function.

if (b.seg.length!=0)

{

circles.attr("cx",R+p)

.attr("cy",R+p); let pathSeg=svg.append("path").attr("id","pathSeg")

.data([b.seg])

.attr("d", d3.line()

.curve(d3.curveCatmullRom))

.attr("fill","none")

.attr("stroke-width","0")

.attr("transform", "translate("+ R+p +"," + R+p + ")"); circles.transition()

.duration(t)

.attrTween("transform", translateAlong(pathSeg.node()))

.attr("fill",d3.hsv(b.h,b.s,b.v))

.attr("r",scaleRadius(b.v));

}

else

{

circles.attr("cx",R+b.x+p)

.attr("cy",R+b.y+p)

.transition().duration(t)

.attr("fill",d3.hsv(b.h,b.s,b.v))

.attr("r",scaleRadius(b.v));

}

Finally, for representing the value V of the HSV color, I changed the radius of the node in proportion to V so it appears to be translating in the direction perpendicular to the screen. Also, to match the background, I overlapped a black circle, of opacity that equals 1-V.

svg.append("circle").attr("id","cvalue")

.attr("cx",R+p).attr("cy",R+p).attr("r",R)

.attr("fill","black").attr("fill-opacity",1-a.v)

.transition().duration(t).ease(d3.easeLinear)

.attr("fill-opacity",1-b.v);

And refresh!

And refresh!

References:

[1] Saif M. Mohammad, NRC Affect Intensity Lexicon

[2] C. Forsyth, Hand-coding a color wheel with canvas (2017)

Reflection: