Commit de1da17d authored by Romain Loth's avatar Romain Loth

connecting topPapers with a mini tweet-search flask server

parent dbecc72d
......@@ -23,6 +23,11 @@
-->
<link href="https://fonts.googleapis.com/css?family=Ubuntu+Condensed" rel="stylesheet" type='text/css'>
<!-- Roboto
Good for tweets if Helvetica is not present
-->
<link href="https://fonts.googleapis.com/css?family=Robot" rel="stylesheet" type='text/css'>
<!-- Crete Round
Original *and* informative :) -->
<link href='https://fonts.googleapis.com/css?family=Crete+Round:400,400italic&subset=latin-ext' rel='stylesheet' type='text/css'>
......@@ -53,6 +58,9 @@
<!-- NB bs2/3 not used for main grid (graph + bars) but inside the bars -->
<link rel="stylesheet" href="libs/css2/freshslider.css" media="screen">
<link type="text/css" href="twitterAPI2/twitterlibs/tweet.light.ltr.css" rel="stylesheet"/>
<link type="text/css" href="twitterAPI2/twitterlibs/custom.css" rel="stylesheet"/>
<!-- JS -->
<!-- <script src="script.js"></script> -->
</head>
......@@ -623,6 +631,7 @@
<!-- <script src="tinawebJS/asyncFA2.js" type="text/javascript" language="javascript"></script> -->
<script src="tinawebJS/Tinaweb.js" type="text/javascript" language="javascript"></script>
<script src="tinawebJS/main.js" type="text/javascript" language="javascript"></script>
<script src="twitterAPI2/twitterlibs/widgets.js" type="text/javascript" language="javascript"></script>
<script type="text/javascript">
// easytabs triggers errors when only one tab TODO fix
......
......@@ -298,18 +298,152 @@ function set_ClustersLegend ( daclass ) {
// });
// }
// }
// a custom variant of twitter plugin written for politoscope
function getTopPapers(type){
console.log("new getTopPapers")
if(TW.getAdditionalInfo){
jsonparams=JSON.stringify(getSelections());
jsonparams=getSelections();
// console.log(jsonparams)
// theHtml = "<p> jsonparams:"+jsonparams+" </p>"
//
$.ajax({
type: 'GET',
url: TW.APINAME,
data: {'query': jsonparams.join(' AND ')},
contentType: "application/json",
success : function(data){
// console.log(data);
var topTweetsHtml = ''
for (var k in data) {
let tweetJson = data[k]
topTweetsHtml += RenderTweet(tweetJson)
}
$("#topPapers").html(topTweetsHtml);
$("#topPapers").show()
},
error: function(){
console.log('Page Not found: getTopPapers');
}
});
jsonparams = jsonparams.split('&').join('__and__');
$("#topPapers").html("<p> jsonparams:"+jsonparams+" </p>");
$("#topPapers").show()
// var theHtml = '<blockquote class="twitter-tweet"><p lang="fr" dir="ltr"><a href="https://twitter.com/hashtag/BuzzFeedHamon?src=hash">#BuzzFeedHamon</a> B. Hamon a une culture large de tous les domaines et est le seul candidat à maitriser tous les sujets et ne pas dire d&#39;intox!</p>&mdash; Nadine Badr Vovelle (@NadineVovelle) <a href="https://twitter.com/NadineVovelle/status/854322537198702592">April 18, 2017</a></blockquote><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>'
//
// $("#topPapers").html(theHtml);
// $("#topPapers").show()
}
}
function clickInsideTweet(e, tweetSrcUrl) {
console.log('>>>>> event target tag', e.target.tag)
console.log("event")
console.log(e)
var tgt = e.target
if (tgt.tagName.toLowerCase() == "a")
window.open(tgt.href, "Link in tweet")
else
window.open(tweetSrcUrl, "Source Tweet")
}
function RenderTweet( tweet) {
var tweet_links = true
var author_url = "http://twitter.com/"+tweet["user"]["screen_name"];
var tweet_url = author_url+"/status/"+tweet["id_str"]
var image_normal = author_url+"/profile_image?size=original";
var image_bigger = "";
if( tweet["user"]["profile_image_url"] ) {
image_normal = tweet["user"]["profile_image_url"]
image_bigger = tweet["user"]["profile_image_url"].replace("_normal","_bigger")
}
var html = ""
html += '\t\t'+ '<blockquote onclick="clickInsideTweet(event, \''+tweet_url+'\')" class="Tweet h-entry tweet subject expanded" cite="'+tweet_url+'" data-tweet-id="'+tweet["id_str"]+'" data-scribe="section:subject">' + '\n';
html += '\t\t\t'+ '<div class="Tweet-header u-cf">' + '\n';
html += '\t\t\t\t'+ '<div class="Tweet-brand u-floatRight">' + '\n';
html += '\t\t\t\t'+ '<span class="Tweet-metadata dateline">' + '\n';
// TODO check datetime iso dates here
html += '\t\t\t\t\t'+ '<a target="_blank" class="u-linkBlend u-url customisable-highlight long-permalink" data-datetime="2012-12-03T18:51:11+000" data-scribe="element:full_timestamp" href="'+tweet_url+'">' + '\n';
html += '\t\t\t\t\t\t'+ '<time class="dt-updated" datetime="2012-12-03T18:51:11+0000" title="'+tweet["created_at"]+'">'+tweet["created_at"]+'</time>' + '\n';
html += '\t\t\t\t\t'+ '</a>' + '\n';
html += '\t\t\t\t'+ '</span>' + '\n';
html += '\t\t\t\t\t'+ '<span class="u-hiddenInWideEnv">' + '\n';
html += '\t\t\t\t\t\t'+ '<a target="_blank" href="'+tweet_url+'" data-scribe="element:logo">' + '\n';
html += '\t\t\t\t\t\t\t'+ '<div class="Icon Icon--twitter " aria-label="" title="" role="presentation"></div>' + '\n';
html += '\t\t\t\t\t\t'+ '</a>' + '\n';
html += '\t\t\t\t\t'+ '</span>' + '\n';
html += '\t\t\t\t'+ '</div>' + '\n';
html += '\t\t\t\t'+ '<div class="Tweet-author u-textTruncate h-card p-author" data-scribe="component:author">' + '\n';
html += '\t\t\t\t\t'+ '<a target="_blank" class="Tweet-authorLink Identity u-linkBlend" data-scribe="element:user_link" href="'+author_url+'">' + '\n';
html += '\t\t\t\t\t\t'+ '<span class="Tweet-authorAvatar Identity-avatar">' + '\n';
html += '\t\t\t\t\t\t\t'+ '<img class="Avatar u-photo" data-scribe="element:avatar" data-src-2x="'+image_bigger+'" src="'+image_normal+'">' + '\n';
html += '\t\t\t\t\t\t'+ '</span>' + '\n';
html += '\t\t\t\t\t\t'+ '<span class="Tweet-authorName Identity-name p-name customisable-highlight" data-scribe="element:name">'+tweet["user"]["name"]+'</span>' + '\n';
html += '\t\t\t\t\t\t'+ '<span class="Tweet-authorScreenName Identity-screenName p-nickname" data-scribe="element:screen_name">@'+tweet["user"]["screen_name"]+'</span>' + '\n';
html += '\t\t\t\t\t'+ '</a>' + '\n';
html += '\t\t\t\t'+ '</div>' + '\n';
html += '\t\t\t'+ '</div>' + '\n';
html += '\t\t\t'+ '<div class="Tweet-body e-entry-content" data-scribe="component:tweet">' + '\n';
html += '\t\t\t\t'+ '<p class="Tweet-text e-entry-title" lang="en" dir="ltr">' + tweet["text"] + '</p>' + '\n';
if( !isUndef(tweet["retweet_count"]) || !isUndef(tweet["favourites_count"]) ) {
html += '\t\t\t\t'+ '<ul class="Tweet-actions" data-scribe="component:actions" role="menu" aria-label="Tweet actions">' + '\n';
if(tweet_links) {
html += '\t\t\t\t\t'+ '<li class="Tweet-action">' + '\n';
html += '\t\t\t\t\t\t'+ '<a target="_blank" class="TweetAction TweetAction--reply web-intent" href="https://twitter.com/intent/tweet?in_reply_to='+tweet["id_str"]+""+'" data-scribe="element:reply">' + '\n';
html += '\t\t\t\t\t\t\t'+ '<div class="Icon Icon--reply TweetAction-icon" aria-label="Reply" title="Reply" role="img"></div>' + '\n';
html += '\t\t\t\t\t\t'+ '</a>' + '\n';
html += '\t\t\t\t\t'+ '</li>' + '\n';
}
if(!isUndef(tweet["retweet_count"])) {
html += '\t\t\t\t\t'+ '<li class="Tweet-action">' + '\n';
html += '\t\t\t\t\t\t'+ '<a target="_blank" class="TweetAction TweetAction--retweet web-intent" href="https://twitter.com/intent/retweet?tweet_id='+tweet["id_str"]+'" data-scribe="element:retweet">' + '\n';
html += '\t\t\t\t\t\t\t'+ '<div class="Icon Icon--retweet TweetAction-icon" aria-label="Retweet" title="Retweet" role="img"></div>' + '\n';
html += '\t\t\t\t\t\t\t'+ '<span class="TweetAction-stat" data-scribe="element:retweet_count" aria-hidden="true">'+tweet["retweet_count"]+'</span>' + '\n';
html += '\t\t\t\t\t\t\t'+ '<span class="u-hiddenVisually">'+tweet["retweet_count"]+' Retweets</span>' + '\n';
html += '\t\t\t\t\t\t'+ '</a>' + '\n';
html += '\t\t\t\t\t'+ '</li>' + '\n';
}
if(!isUndef(tweet["favourites_count"])) {
html += '\t\t\t\t\t'+ '<li class="Tweet-action">' + '\n';
html += '\t\t\t\t\t\t'+ '<a target="_blank" class="TweetAction TweetAction--favorite web-intent" href="https://twitter.com/intent/favorite?tweet_id='+tweet["id_str"]+'" data-scribe="element:favorite">' + '\n';
html += '\t\t\t\t\t\t\t'+ '<div class="Icon Icon--favorite TweetAction-icon" aria-label="Favorite" title="Favorite" role="img"></div>' + '\n';
html += '\t\t\t\t\t\t\t'+ '<span class="TweetAction-stat" data-scribe="element:favourites_count" aria-hidden="true">'+tweet["favourites_count"]+'</span>' + '\n';
html += '\t\t\t\t\t\t\t'+ '<span class="u-hiddenVisually">'+tweet["favourites_count"]+' favorites</span>' + '\n';
html += '\t\t\t\t\t\t'+ '</a>' + '\n';
html += '\t\t\t\t\t'+ '</li>' + '\n';
}
html += '\t\t\t\t'+ '</ul>' + '\n';
}
html += '\t\t\t'+ '</div>' + '\n';
html += '\t\t'+ '</blockquote>' + '\n';
// html += '\t\t'+ '<br>' + '\n';
return html;
}
//FOR UNI-PARTITE
function selectionUni(currentNode){
......
......@@ -132,7 +132,7 @@
#ctlzoom {
position: fixed;
right: 260px;
right: 410px;
bottom: 0;
list-style: none;
padding: 0;
......@@ -287,3 +287,10 @@
#current-selection {
width: 100%; /* 100% within lefttopbox */
}
/* tweets inside topPapers */
.EmbeddedTweet-tweet {
width: 295px;
max-width: 295px;
}
......@@ -20,7 +20,7 @@ html, body {
top: 105px;
bottom: 0;
left: 0;
right: 250px;
right: 400px;
z-index: 1;
/* non selectable: we've got our own events here */
......@@ -35,7 +35,7 @@ html, body {
top: 105px;
bottom: 0;
right: 0;
width: 250px;
width: 400px;
z-index: 2;
border-left: 1px #222 solid;
overflow-y: scroll;
......
......@@ -21,7 +21,7 @@ var TW = {}
// // "data/example.json",
// // "data/Elisa__Omodei.gexf",
];
TW.APINAME = "LOCALDB/"; // TODO use in topPapers
TW.APINAME = "http://127.0.0.1:5000/twitter_search";
TW.tagcloud_limit = 50;
TW.bridge={};
TW.bridge["forFilteredQuery"] = "php/bridgeClientServer_filter.php";
......
......@@ -920,7 +920,9 @@ TinaWebJS = function ( sigmacanvas ) {
winResizeTimeout = setTimeout(function() {
console.log('did refresh')
TW.partialGraph.refresh()
theHtml.classList.remove('waiting');
if (theHtml.classList) {
theHtml.classList.remove('waiting');
}
}, 3000)
}, true)
......
......@@ -17,6 +17,8 @@ function cancelSelection (fromTagCloud, settings) {
//Nodes colors go back to normal
overNodes=false;
// £TODO case with return to alternate colors
if (TW.partialGraph.settings('drawEdges')) {
for(let i=0;i<TW.nEdges;i++){
let e = TW.partialGraph.graph.edges(TW.edgeIds[i])
......@@ -584,7 +586,7 @@ function unHide(nodeId) {
// case default: we just change the flags
// - greyish color was precomputed in prepareNodesRenderingProperties
// as n.customAttrs.defgrey_color
// - renderer will see the flags and and handle the case accordingly
// - renderer will see the flags and handle the case accordingly
// cases when coloredBy (ex: centrality): color must be recomputed here
function greyEverything(notDefaultColors){
......
......@@ -721,6 +721,7 @@ function clustersBy(daclass) {
var newval_color = Math.round( ( Min_color+(NodeID_Val[nid]["round"]-real_min)*((Max_color-Min_color)/(real_max-real_min)) ) );
var hex_color = rgbToHex(255, (255-newval_color) , 0)
TW.partialGraph.graph.nodes(nid).color = hex_color
TW.partialGraph.graph.nodes(nid).customAttrs.alt_color = hex_color
var newval_size = Math.round( ( Min_size+(NodeID_Val[nid]["round"]-real_min)*((Max_size-Min_size)/(real_max-real_min)) ) );
TW.partialGraph.graph.nodes(nid).size = newval_size;
......@@ -749,23 +750,25 @@ var totalsPerBinMin = {
}
// Edge-colour by source-target nodes-colours combination
// TODO rm because duplicate with edgeRGB
function repaintEdges() {
var v_edges = getVisibleEdges();
for(var e in v_edges) {
var e_id = v_edges[e].id;
var a = TW.partialGraph.graph.nodes(v_edges[e].source).color;
var b = TW.partialGraph.graph.nodes(v_edges[e].target).color;
a = hex2rga(a);
b = hex2rga(b);
var r = (a[0] + b[0]) >> 1;
var g = (a[1] + b[1]) >> 1;
var b = (a[2] + b[2]) >> 1;
TW.partialGraph.graph.edges(e_id).color = "rgba("+[r,g,b].join(",")+",0.5)";
// also keep components array (useful if we change opacity when selected)
TW.partialGraph.graph.edges(e_id).customAttrs.rgb = [r,g,b]
}
console.log('skipping repaintEdges')
// var v_edges = getVisibleEdges();
// for(var e in v_edges) {
// var e_id = v_edges[e].id;
// var a = TW.partialGraph.graph.nodes(v_edges[e].source).color;
// var b = TW.partialGraph.graph.nodes(v_edges[e].target).color;
// a = hex2rga(a);
// b = hex2rga(b);
// var r = (a[0] + b[0]) >> 1;
// var g = (a[1] + b[1]) >> 1;
// var b = (a[2] + b[2]) >> 1;
//
// TW.partialGraph.graph.edges(e_id).color = "rgba("+[r,g,b].join(",")+",0.5)";
//
// // also keep components array (useful if we change opacity when selected)
// TW.partialGraph.graph.edges(e_id).customAttrs.rgb = [r,g,b]
// }
}
// rewrite of clustersBy with binning and for attributes that can have negative float values
......@@ -795,11 +798,15 @@ function colorsRelByBins(daclass) {
// £TODO put colors and thresholds as params or calculate thresholds like eg d3.histogram
if (daclass == 'age') {
tickThresholds = [-1000000,1992,1994,1996,1998,2000,2002,2004,2006,2008,2010,2012,2014,2016]
tickThresholds = [-1000000,1992,1994,1996,1998,2000,2002,2004,2006,2008,2010,2012,2014,1451606400000]
// and add a grey color for the first timeperiod
binColors.unshift("#F9F7ED")
console.log("======> doing AGE")
}
else if (daclass == 'growth_rate') {
tickThresholds = [0,.001,.01,.1,.5,1,1.5,2,2.5,3,3.5,5, 1000000]
binColors[4] = ""
binColors = [
"#005197", //blue binMin -∞
......@@ -818,32 +825,50 @@ function colorsRelByBins(daclass) {
}
// new strategy
// do first loop entirely to get percentiles => bins, then modify alt_color
// get the nodes
var v_nodes = getVisibleNodes();
for(var i in v_nodes) {
var theId = v_nodes[i].id
var theNode = TW.Nodes[ theId ]
var attval = ( isUndef(theNode.attributes) || isUndef(theNode.attributes[daclass]) )? v_nodes[i][daclass]: theNode.attributes[daclass];
var theVal = parseFloat(attval)
var foundBin = false
console.log('theVal:',theVal)
if( !isNaN(theVal) ) { //is float
// iterate over bins
for(var j=0 ; j < tickThresholds.length-1; j++) {
var binMin = tickThresholds[j]
var binMax = tickThresholds[(j+1)]
if((theVal >= binMin) && (theVal < binMax)) {
TW.partialGraph._core.graph.nodesIndex[theId].binMin = binMin
TW.partialGraph._core.graph.nodesIndex[theId].color = binColors[j]
// TW.partialGraph._core.graph.nodesIndex[theId].binMin = binMin
// TW.partialGraph._core.graph.nodesIndex[theId].color = binColors[j]
TW.partialGraph.graph.nodes(theId).binMin = binMin
TW.partialGraph.graph.nodes(theId).color = binColors[j]
TW.partialGraph.graph.nodes(theId).customAttrs.alt_color = binColors[j]
foundBin = true
totalsPerBinMin[binMin]++
break
}
}
if (!foundBin) {
TW.partialGraph.graph.nodes(theId).binMin = null
TW.partialGraph.graph.nodes(theId).color = '#555'
TW.partialGraph.graph.nodes(theId).customAttrs.alt_color = '#555'
}
}
}
// [ Edge-colour by source-target nodes-colours combination ]
repaintEdges()
// repaintEdges()
// [ / Edge-colour by source-target nodes-colours combination ]
set_ClustersLegend ( null )
......@@ -877,6 +902,9 @@ function colorsBy(daclass) {
for(var i in v_nodes) {
var original_node_color = TW.Nodes[ v_nodes[i].id ].color
TW.partialGraph.graph.nodes(v_nodes[i].id).color = original_node_color
// reset the alt_color valflag
TW.partialGraph.graph.nodes(v_nodes[i].id).customAttrs.alt_color = null
}
}
else {
......@@ -887,9 +915,12 @@ function colorsBy(daclass) {
var the_node = TW.Nodes[ v_nodes[i].id ]
var attval = ( isUndef(the_node.attributes) || isUndef(the_node.attributes[daclass]) )? v_nodes[i][daclass]: the_node.attributes[daclass];
TW.partialGraph.graph.nodes(v_nodes[i].id).color = randomColorList[ attval ]
TW.partialGraph.graph.nodes(v_nodes[i].id).customAttrs.alt_color = randomColorList[ attval ]
}
}
// £TODO remove another duplicate of edgeRGB
// [ Edge-colour by source-target nodes-colours combination ]
var v_edges = getVisibleEdges();
for(var e in v_edges) {
......
#! /usr/bin/python3
"""
Micro-server for testing twitter top tweets queries
"""
__author__ = "Romain Loth"
__copyright__ = "Copyright 2017 ISCPIF-CNRS"
__license__ = "LGPL"
__version__ = "0.5"
__email__ = "romain.loth@iscpif.fr"
__status__ = "dev"
from json import load
from flask import Flask, request
from urllib.parse import quote
import twitter
from flask.ext.cors import CORS, cross_origin
app = Flask(__name__)
cors = CORS(app)
app.config['CORS_HEADERS'] = 'Content-Type'
# ---- initialize twitter api with credentials ----
keys_file = open("keys.json")
credentials = load(keys_file)
keys_file.close()
TAPI = twitter.Api(consumer_key=credentials['consumer_key'],
consumer_secret=credentials['consumer_secret'],
access_token_key=credentials['access_token_key'],
access_token_secret=credentials['access_token_secret'])
print("logged in to twitter as", TAPI.VerifyCredentials().screen_name)
# query context: constant for one app
QCONTEXT = "(Fillon OR Macron OR JLM2017 OR Mélenchon OR #Marine2017 OR @MLP_officiel OR Hamon OR Presidentielle2017)"
@app.route('/twitter_search')
# @cross_origin(origin="twjs.org")
@cross_origin()
def searcher():
if 'query' in request.args:
# prepare query
# from args
q = request.args['query']
# TODO here sanitize
q = "%s AND %s" % (q, QCONTEXT)
q = quote(q)
q = "q=%s" % q + "&result_type=recent&count=5"
search_res = TAPI.GetSearch(raw_query=q)
sending_json = "[%s]" % ",".join([status.AsJsonString() for status in search_res])
else:
sending_json = '{"error":"no query??"}'
response = app.response_class(
response=sending_json,
status=200,
mimetype='application/json'
)
return response
########### MAIN ###########
if __name__ == "__main__":
app.run(debug=True)
blockquote.Tweet {
display: inline-block !important;
font-family: Roboto, "Helvetica Neue", "Segoe UI", Calibri, sans-serif !important;
font-size: 12px !important;
font-weight: bold !important;
line-height: 16px !important;
border-color: #eee #ddd #bbb !important;
border-radius: 5px !important;
border-style: solid !important;
border-width: 1px !important;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15) !important;
margin: 10px 5px !important;
padding: 0 16px 16px 16px !important;
min-width: 330px !important;
width: 330px !important;
max-width: 330px !important;
}
p.Tweet-text {
font-size: 16px !important;
font-weight: normal !important;
line-height: 20px !important;
min-height: 90px !important;
}
.SandboxRoot { display: none !important; }
blockquote.Tweet a {
color: inherit !important;
font-weight: normal !important;
text-decoration: none !important;
outline: 0 none !important;
}
blockquote.Tweet a:hover,
blockquote.Tweet a:focus {
text-decoration: underline !important;
}
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment