421 lines
12 KiB
Smalltalk
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
|
|
]
|