Grafoscopio/src/Dataviz/Tweet.class.st

421 lines
12 KiB
Smalltalk

"
I'm Twitt
I store twitts from twitter (http://twitter.com).
"
Class {
#name : #Tweet,
#superclass : #Object,
#instVars : [
'date',
'message',
'profile',
'url',
'mentions',
'links',
'hashtags',
'type'
],
#category : #'Dataviz-Twitter'
}
{ #category : #accessing }
Tweet >> date [
^ date
]
{ #category : #accessing }
Tweet >> date: anObject [
date := anObject
]
{ #category : #accessing }
Tweet >> hashtags [
^ hashtags
]
{ #category : #accessing }
Tweet >> hashtags: anObject [
hashtags := anObject
]
{ #category : #'data scrapping' }
Tweet >> impact [
"Computes the maximum amount of followers that a tweet can reach, including the retweets.
It doesn't take into account that retweeters can share followers and in future versions
this should be computed, even if we don't have a full list of followers.
Also the computing time could be sustancially improved by extracting only followers data
from the profiles instead of scrapping all and by having a better cache which includes
prescrapped data.
'Real time' is not the main issue here, but 'slow data'.
For example we could include snapshots of the retweeters profiles and to aks for a
commented retweet to reach new followers"
| retweetsHtml retweetsTree retweeters impactData |
retweetsHtml := ZnClient new get: 'http://mutabit.com/deltas/repos.fossil/datapolis/doc/tip/Data/Sources/Single/', ((self url splitOn: '//') at: 2), '/retweets.html'.
retweetsTree := Soup fromString: retweetsHtml.
retweeters := (retweetsTree findAllTagsByClass: 'username')
collect: [:each | each text copyReplaceAll: '@' with: ''].
impactData := Dictionary new.
impactData
at: 'retweeters' put: retweeters size;
at: 'reach' put: (retweeters collect: [:each | (TwitterProfile new scrapDataForProfile: each) followers]) sum.
^ impactData.
]
{ #category : #'data scrapping' }
Tweet >> impactFor: aTweetUrl [
"Computes the maximum amount of followers that a tweet can reach, including the retweets.
It doesn't take into account that retweeters can share followers and in future versions
this should be computed, even if we don't have a full list of followers.
Also the computing time could be sustancially improved by extracting only followers data
from the profiles instead of scrapping all and by having a better cache which includes
prescrapped data.
'Real time' is not the main issue here, but 'slow data'.
For example we could include snapshots of the retweeters profiles and to aks for a
commented retweet to reach new followers"
| retweetsHtml retweetsTree retweeters impactData |
retweetsHtml := ZnClient new get: 'http://mutabit.com/deltas/repos.fossil/datapolis/doc/tip/Data/Sources/Single/', ((aTweetUrl splitOn: '//') at: 2), '/retweets.html'.
retweetsTree := Soup fromString: retweetsHtml.
retweeters := (retweetsTree findAllTagsByClass: 'username')
collect: [:each | each text copyReplaceAll: '@' with: ''].
impactData := Dictionary new.
impactData
at: 'retweeters' put: retweeters size;
at: 'reach' put: (retweeters collect: [:each | (TwitterProfile new scrapFollowersForProfile: each)]) sum.
^ impactData.
]
{ #category : #initialization }
Tweet >> initialize [
"Creates a new TwitterProfile object"
super initialize.
]
{ #category : #'data visualization' }
Tweet >> labeledCircleSized: diameter upperLabel: label1 bottomLabel: label2 inView: v [
| circle ex1 ex2 line label1Temp label2Temp radious origin background els |
background := (RTBox new color: Color transparent) element.
v add: background.
origin := (0@0).
radious := diameter / 2.
circle := (RTEllipse new size: diameter"; borderColor: Color black; color: Color transparent") element.
ex1 := RTBox element.
ex2 := RTBox element.
circle translateTo: origin.
ex1 translateTo: ((radious negated @ 0) + circle center).
ex2 translateTo: (radious @ 0) + circle center.
line := (RTLine new; color: Color black) edgeFrom: ex1 to: ex2.
v add: circle; add: line.
label1Temp := (RTLabel text: label1) element.
label2Temp := (RTLabel text: label2) element.
v add: label1Temp ; add: label2Temp .
label1Temp translateTo: (0 @ -12) + circle center.
label2Temp translateTo: (0 @ 12) + circle center.
els := RTGroup with: label1Temp with: label2Temp with: circle with: ex1 with: ex2.
RTNest new on: background nest: els.
^ background
]
{ #category : #accessing }
Tweet >> links [
^ links
]
{ #category : #accessing }
Tweet >> links: anObject [
links := anObject
]
{ #category : #accessing }
Tweet >> mentions [
^ mentions
]
{ #category : #accessing }
Tweet >> mentions: anObject [
mentions := anObject
]
{ #category : #'data visualization' }
Tweet >> mentionsClusterSeparated: aDistance inView: aView [
"I return a graphic of all avatars mentioned in a tweet, layered as a cluster a separated
by aDistance"
| container avatars profiles |
"Profiles: Create profiles collecting information for all users mentioned in the tweet"
profiles := OrderedCollection new.
self mentions do: [:eachMention |
profiles add: (TwitterProfile new scrapDataForProfile: eachMention)].
"Avatars: Use the profiles to create a collection of avatar images which are draggable
elements on a view"
avatars := OrderedCollection new.
profiles do: [:eachProfile |
avatars add: (RTBitmap new form: eachProfile avatar) element ].
RTCircleLayout new
initialRadius: (avatars size)*aDistance;
on: avatars.
container := (RTBox new color: Color transparent) element .
RTNest new on: container nest: avatars.
"Adding avatars and its labels"
aView addAll: avatars.
aView add: container.
1 to: (avatars size) do: [:i |
(avatars at: i) @ (RTLabelled new
text: (('@',(self mentions at: i)));
fontSize: 35;
color: (Color black))].
^ container
]
{ #category : #accessing }
Tweet >> message [
^ message
]
{ #category : #accessing }
Tweet >> message: anObject [
message := anObject
]
{ #category : #accessing }
Tweet >> profile [
^ profile
]
{ #category : #accessing }
Tweet >> profile: anObject [
profile := anObject
]
{ #category : #'data scrapping' }
Tweet >> scrapDataFromUrl: aTweetUrl [
"Scraps most of the data in the page of a aTweetUrl. Most of the tweets are prestored now, but in the future
it will combine prestored data and live streaming data"
| tweetSourceHtml tweetSourceTree hour dateTmp day month year|
tweetSourceHtml := ZnClient new get:
'http://mutabit.com/deltas/repos.fossil/datapolis/doc/tip/Data/Sources/Single/',
((aTweetUrl splitOn: '//') at: 2), '/tweet.html'.
tweetSourceTree := Soup fromString: tweetSourceHtml.
self url: aTweetUrl .
self profile: ((aTweetUrl splitOn: '/') at: 4).
self message: ((((tweetSourceTree findAllTagsByClass: 'tweet') at: 1) children at: 6) children at: 2).
self mentions: ((self message findAllTagsByClass: 'twitter-atreply')
collect: [:each | (each attributeAt: 'href') copyReplaceAll: '/' with: '']).
self hashtags: ((self message findAllTagsByClass: 'twitter-hashtag') collect: [:each | each text]).
self links: ((self message findAllTagsByClass: 'twitter-timeline-link')
collect: [:each | each attributeAt: 'data-url']).
hour := ((((tweetSourceTree findAllTagsByClass: 'tweet') at: 1) children at: 8) children at: 2).
dateTmp := (((((tweetSourceTree findAllTagsByClass: 'tweet') at: 1) children at: 8) children at: 4) text) splitOn: ' '.
day := dateTmp at: 3.
month := dateTmp at: 4.
year := '20', (dateTmp at: 5).
self date: (year, month, day, hour text, hour nextSibling text) asDateAndTime.
]
{ #category : #'data visualization' }
Tweet >> showInView: aView [
"shows the general information (author, author's avatar and text) of the tweet in a view"
| tweetPanel tweetText holder avatar |
"Tweet text"
(self message text size > 50)
ifTrue:[
tweetText := RTLabel new
text: (self splitByLines at: 1), String cr,
(self splitByLines at: 2), String cr, String cr,
(self date asString)]
ifFalse: [tweetText := RTLabel new
text: (self splitByLines at: 1), String cr, String cr,
(self date asString) ].
tweetText height: 35.
tweetText := tweetText element.
"Avatar"
avatar := (RTBitmap new form:
((TwitterProfile new scrapDataForProfile: self profile) avatar)) element.
avatar translateBy: (-30*(30)@0).
"Putting tweet elements together"
tweetPanel := OrderedCollection new.
tweetPanel add: avatar; add: tweetText.
holder := (RTBox new color: Color transparent) element @ RTDraggable.
RTNest new on: holder nest: tweetPanel.
"Adding elements and labels to the view"
aView add: tweetText.
aView add: avatar.
aView add: holder.
avatar @ (RTLabelled new
text: '@', (self profile);
fontSize: 35;
color: Color black;
top;
offsetOnEdge: 1 ).
^ holder.
]
{ #category : #'data visualization' }
Tweet >> showInView: aView sized: aSize [
"shows the general information (author, author's avatar and text) of the tweet in a view"
| tweetPanel tweetText holder avatar |
"Tweet text"
(self message text size > 50)
ifTrue:[
tweetText := RTLabel new
text: (self splitByLines at: 1), String cr,
(self splitByLines at: 2), String cr, String cr,
(self date asString)]
ifFalse: [tweetText := RTLabel new
text: (self splitByLines at: 1), String cr, String cr,
(self date asString) ].
tweetText height: aSize.
tweetText := tweetText element.
"Avatar"
avatar := (RTBitmap new form:
((TwitterProfile new scrapDataForProfile: self profile) avatar)) element.
avatar translateBy: (-30*aSize@0).
"Putting tweet elements together"
tweetPanel := OrderedCollection new.
tweetPanel add: avatar; add: tweetText.
holder := (RTBox new color: Color transparent) element @ RTDraggable.
RTNest new on: holder nest: tweetPanel.
"Adding elements and labels to the view"
aView add: tweetText.
aView add: avatar.
aView add: holder.
avatar @ (RTLabelled new
text: '@', (self profile);
fontSize: 35;
color: Color black;
top;
offsetOnEdge: 1 ).
^ holder.
]
{ #category : #'data visualization' }
Tweet >> silenceMapFor: arg1 [
| tmp1 tmp2 tmp3 tmp4 tmp5 tmp6 tmp7 tmp8 tmp9 tmp11 tmp13 tmp15 tmp17 tmp19 tmp21 |
self scrapDataFromUrl: arg1.
tmp5 := (Date today - self date) days.
tmp7 := self impactFor: arg1.
tmp11 := RTBox new.
tmp11 color: Color red.
tmp3 := tmp11 size: 200.
tmp3 := tmp3 element.
tmp3 translateBy: (tmp5 * -50) @ 0.
tmp1 := RTView new.
tmp6 := self mentionsClusterSeparated: 70 inView: tmp1.
tmp2 := self showInView: tmp1 sized: 35.
tmp2 translateBy: (tmp5 * -25) @ -600.
tmp1 add: tmp3.
tmp4 := RTEdge from: tmp6 to: tmp3.
tmp13 := RTGradientColoredLine new.
tmp13
colors: (Array with: (Color white alpha: 0.3) with: (Color red alpha: 0.9));
precision: 100;
width: 20.
tmp1 add: tmp4 + tmp13 gradientColorShape.
tmp15 := RTLabel new.
tmp15 text: tmp5 asString , ' días sin respuesta'.
tmp8 := (tmp15 height: tmp5 * 2.5) element @ RTDraggable.
tmp8 translateBy: (tmp5 * -25) @ -105.
tmp17 := RTLabel new.
tmp17
text: 'al ' , Date today asString , ' y contando...';
height: tmp5 * 1.
tmp9 := (tmp17 color: Color gray) element @ RTDraggable.
tmp9 translateBy: (tmp5 * -15) @ 50.
tmp1
add: tmp8;
add: tmp9.
tmp19 := RTLabelled new.
tmp19
text: (tmp7 at: 'retweeters') asString , ' retweets';
fontSize: tmp5 * 2.
tmp3 @ (tmp19 color: Color gray).
tmp21 := RTLabelled new.
tmp21
text: (tmp7 at: 'reach') asString , ' lectores' , String cr , '(max)';
fontSize: tmp5 * 2;
color: Color gray.
tmp3 @ tmp21 below.
tmp1 view canvas focusOnCenterScaled.
^ tmp1 @ RTDraggableView
]
{ #category : #'as yet unclassified' }
Tweet >> splitByLines [
"Splits the message text of the tweet in lines, in case of being too long for a better rendering
in silenceMap and other views."
| summa i charLimit words wordSizes lines |
words := self message text splitOn: ' '.
wordSizes := words collect: [:eachWord | eachWord size].
i := 1.
summa := 0.
charLimit := 50.
[summa < charLimit] whileTrue: [
summa := (wordSizes at: i) + summa.
i := i +1].
lines := OrderedCollection new.
lines add: (Character space join: (words copyFrom: 1 to: i)).
lines add: (Character space join: (words copyFrom: i + 1 to: words size)).
^ lines
]
{ #category : #accessing }
Tweet >> type [
^ type
]
{ #category : #accessing }
Tweet >> type: anObject [
type := anObject
]
{ #category : #accessing }
Tweet >> url [
^ url
]
{ #category : #accessing }
Tweet >> url: anObject [
url := anObject
]