Google terminated image charts. Now use JavaScript charts.
David Blume

David Blume commited on 2019-03-22 23:48:17
Showing 1 changed files, with 78 additions and 101 deletions.

... ...
@@ -25,11 +25,12 @@ import json
25 25
 import xml
26 26
 import operator
27 27
 import cgi
28
+import cStringIO
28 29
 import smtp_creds  # Your own credentials, used in send_email()
29 30
 
30 31
 debug = True
31 32
 any_entry_added = False
32
-tags_to_post = {'apple', 'google'}
33
+tags_to_post = {'apple', 'google', 'roku'}
33 34
 authors_to_post = ['michael arrington',]
34 35
 
35 36
 # TODO 2018-01-18: Maybe combine fb_likes with bf_shares or something...
... ...
@@ -38,9 +39,8 @@ rhs_metric_times = 'comment_times'
38 39
 
39 40
 localdir = ''
40 41
 
41
-html_head = """
42
-<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>
43
-<HTML><HEAD>
42
+html_head = """<!DOCTYPE html>
43
+<html><head>
44 44
   <title>TechCrunch Feed Filter</title>
45 45
   <!-- <link rel="alternate" type="application/rss+xml" title="RSS feed" href="http://techcrunch.dlma.com/rss_feed.xml" /> -->
46 46
   <link rel="alternate" type="application/rss+xml" title="RSS feed" href="http://feeds.feedburner.com/TrendingAtTechcrunch" />
... ...
@@ -57,12 +57,30 @@ html_head = """
57 57
     tr.even { background:#%s; padding: 2em; }
58 58
     tr.odd { background:#%s; padding-bottom: 2em; }
59 59
   </style>
60
-</HEAD>
61
-<BODY>
60
+  <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
61
+  <script type="text/javascript">
62
+    google.charts.load('current', {'packages':['corechart']});
63
+    google.charts.setOnLoadCallback(drawChart);
64
+    function drawChart() {
65
+      var options = {
66
+        width:300,
67
+        height:68,
68
+        pointSize:0.1,
69
+        dataOpacity:1.0,
70
+        series: { 0: {targetAxisIndex:0}, 1: {targetAxisIndex:1, color:'limegreen'} },
71
+        vAxis: { gridlines: {count: 0}, maxValue: 1 },
72
+        hAxis: { gridlines: {count: 0}, ticks: [] },
73
+        vAxes: { 0: {textStyle: {fontSize: 11, color: 'blue'} }, 1: {viewWindowMode: 'maximized', baselineColor: '#A0D0A0', textStyle: {fontSize: 11, color: 'limegreen'} } },
74
+      };
75
+%s
76
+    }
77
+  </script>
78
+</head>
79
+<body>
62 80
 <div align='center'><h3>TechCrunch Feed Filter</h3></div>
63 81
 This page shows what analysis is done to filter the noise away from the Techcrunch feed into
64 82
 <a href="http://feeds.feedburner.com/TrendingAtTechcrunch"> a more concise feed <img src="feed.png" alt="feed" height="14" width="14"></a>.
65
-<a href="http://david.dlma.com/blog/my-techcrunch-feed-filter">Learn more about the Feed Filter</a>.<br /><br />
83
+<a href="https://david.dlma.com/blog/my-techcrunch-feed-filter">Learn more about the Feed Filter</a>.<br /><br />
66 84
 """
67 85
 
68 86
 html_footer = """
... ...
@@ -70,13 +88,21 @@ html_footer = """
70 88
 </div><br />
71 89
 <div align='center'>Thanks to <a href="http://www.feedparser.org/">The Universal Feed Parser module</a>,
72 90
 <a href="http://pyyaml.org/">PyYAML</a> and <a href="http://code.google.com/apis/chart/">Google Charts</a>.<br />
73
-<a href="http://git.dlma.com/techcrunch.git/">source</a> &bull; <a href="techcrunch.yaml">raw data</a> &bull; <a href="stats.txt">status</a><br />&copy; 2011 <a href="http://david.dlma.com">David Blume</a></div><br />
74
-</BODY>
75
-</HTML>
91
+<a href="http://git.dlma.com/techcrunch.git/">source</a> &bull; <a href="techcrunch.yaml">raw data</a> &bull; <a href="stats.txt">status</a><br />&copy; 2011 <a href="https://david.dlma.com">David Blume</a></div><br />
92
+</body>
93
+</html>
94
+"""
95
+
96
+chart_data_header = """      var data = google.visualization.arrayToDataTable([
97
+        ['', 'Comments', 'Shares', {'type': 'string', 'role': 'style'}],
98
+"""
99
+chart_data_middle = """      ]);
100
+      var chart = new google.visualization.LineChart(document.getElementById('chart%d'));
101
+      options.backgroundColor = '#%s';
76 102
 """
77 103
 
78 104
 img_width = 300
79
-img_height = 50
105
+img_height = 68
80 106
 
81 107
 series_1_color = "0000FF"
82 108
 series_2_color = "00AA00"
... ...
@@ -118,10 +144,11 @@ def index_id(a_list, op, elem):
118 144
         return -1
119 145
 
120 146
 
121
-def make_chart_url(time_posted, lhs_times, lhs_values, rhs_times,
122
-                   rhs_values, threshold_value, is_odd_row, tag_hit):
147
+def write_chart_data(time_posted, lhs_times, lhs_values, rhs_times,
148
+                   rhs_values, threshold_value, image_index, tag_hit, chart_io):
123 149
 #    lhs_times, lhs_values = zip(*comments)
124 150
 #    rhs_times, rhs_values = zip(*rhs)
151
+    is_odd_row = image_index % 2
125 152
 
126 153
     if not len(lhs_times):
127 154
         lhs_times = [time_posted,]
... ...
@@ -135,11 +162,6 @@ def make_chart_url(time_posted, lhs_times, lhs_values, rhs_times,
135 162
     lhs_times = [(i - time_posted) / 1800 for i in lhs_times]
136 163
     rhs_times = [(i - time_posted) / 1800 for i in rhs_times]
137 164
 
138
-    max_comment_time = max(lhs_times)
139
-    max_comment_value = max(lhs_values)
140
-    max_rhs_time = max(rhs_times)
141
-    max_rhs_value = max(rhs_values)
142
-
143 165
     met_threshold_pt = -1
144 166
     if threshold_value != -1:
145 167
         met_threshold_pt = index_id(rhs_values, operator.ge, threshold_value)
... ...
@@ -151,54 +173,33 @@ def make_chart_url(time_posted, lhs_times, lhs_values, rhs_times,
151 173
 
152 174
     if is_odd_row != 0:
153 175
         bg_color = even_background
154
-        watermark_color = even_watermark
155 176
     else:
156 177
         bg_color = odd_background
157
-        watermark_color = odd_watermark
158 178
 
159
-    if len(lhs_values) < 8 and len(lhs_values) > 1:
160
-        # max_comment_value *= 2
161
-        pass
162
-    elif len(lhs_values) == 1:
163
-        min_comment_value = 0
164
-    if len(rhs_values) < 8 and len(rhs_values) > 1:
165
-        # max_rhs_value *= 2
166
-        pass
167
-    elif len(rhs_values) == 1:
168
-        min_rhs_value = 0
169
-
170
-    min_comment_value = 0
171
-    min_rhs_value = 0
172
-
173
-    chart_url = "http://chart.apis.google.com/chart?cht=lxy&chco=%s,%s&chs=%dx%d&chxs=0,%s|1,%s" % \
174
-                (series_1_color, series_2_color, img_width, img_height, series_1_color, series_2_color)
175
-    chart_url += "&chd=t:%s|%s|%s|%s" % (','.join([str(n) for n in lhs_times]),
176
-                                         ','.join([str(n) for n in lhs_values]),
177
-                                         ','.join([str(n) for n in rhs_times]),
178
-                                         ','.join([str(n) for n in rhs_values]))
179
-    # TODO: Consider watermark levels, like:
180
-    # chm=h,B0B0B0,1,0.3,1|r,E0E0E0,0,0,0.5
181
-    if max_rhs_value > 0:
182
-        threshold_percent = max(0, min((float(threshold_value) / max_rhs_value) - 0.01, 1.0))
183
-    else:
184
-        threshold_percent = 1.0
185
-    chart_url += "&chm=r,%s,0,0,%1.3f" % (watermark_color, threshold_percent)
186
-    if met_threshold_pt != -1:
179
+    chart_io.write(chart_data_header)
180
+    for i in range(8):
181
+        if i == met_threshold_pt:
187 182
             if tag_hit:
188
-            dot_color = tag_color
189
-            dot_shape = 'd'
183
+                style = "'point { size: 5; fill-color: #FF0000; shape-type: diamond}'"
184
+            else:
185
+                style = "'point { size: 5; fill-color: #FF8C00; }'"
186
+        else:
187
+            style = "null"
188
+        if i < len(lhs_values):
189
+            lhs_value = str(lhs_values[i])
190 190
         else:
191
-            dot_color = threshold_color
192
-            dot_shape = 'o'
193
-        chart_url += "|%s,%s,1,%d,10" % (dot_shape, dot_color, met_threshold_pt)
194
-    chart_url += "&chxt=y,r&chxl=0:|%d|%d|1:|%d|%d&chds=%d,%d,%d,%d,%d,%d,%d,%d" % \
195
-                 (min_comment_value, max_comment_value, min_rhs_value, max_rhs_value,
196
-                  0, max(7, max_comment_time),
197
-                  min_comment_value, max_comment_value,
198
-                  0, max(7, max_rhs_time),
199
-                  min_comment_value, max_rhs_value)
200
-    chart_url += "&chf=bg,s,%s&chdl=comments|shares" % (bg_color,)
201
-    return chart_url
191
+            lhs_value = "null"
192
+        if i < len(rhs_values):
193
+            rhs_value = str(rhs_values[i])
194
+        else:
195
+            rhs_value = "null"
196
+        chart_io.write("        [%d,  %s,        %s, %s],\n" % (i, lhs_value, rhs_value, style))
197
+    chart_io.write(chart_data_middle % (image_index, bg_color))
198
+    if met_threshold_pt == -1 and not tag_hit:
199
+        chart_io.write("      delete options.vAxes[1].baseline;\n")
200
+    else:
201
+        chart_io.write("      options.vAxes[1].baseline = %d;\n" % (threshold_value,))
202
+    chart_io.write("      chart.draw(data, options);\n\n")
202 203
 
203 204
 
204 205
 def process_feed(yaml_items):
... ...
@@ -432,27 +433,6 @@ def Get_fb_stats(url_string):
432 433
     return shares, comments, likes
433 434
 
434 435
 
435
-def save_image(url_string, file_path):
436
-    try:
437
-        f = urllib2.urlopen(url_string)
438
-        data = f.read()
439
-        f.close()
440
-    except (urllib2.URLError, httplib.BadStatusLine) as e:
441
-        if hasattr(e, 'reason'): # URLError
442
-            print "save_image: Error attempting to create", file_path[file_path.rfind('/')+1:], "Reason:", e.reason
443
-        elif hasattr(e, 'code'): # URLError
444
-            print "save_image: Error attempting to create", file_path[file_path.rfind('/')+1:], "Code:", e.code
445
-        else:
446
-            print "save_image: Error from urlopen", e
447
-        return url_string
448
-
449
-    if len(data) > 50:
450
-        with open(file_path, 'wb') as f:
451
-            f.write(data)
452
-        return 'cache/' + os.path.basename(file_path)
453
-    return url_string
454
-
455
-
456 436
 def make_index_html(yaml_items, weekend_stats, weekday_stats):
457 437
     """Writes a static index.html file from the YAML items."""
458 438
     cur_time = int(time.time())
... ...
@@ -462,31 +442,33 @@ def make_index_html(yaml_items, weekend_stats, weekday_stats):
462 442
 
463 443
     files_to_delete = glob.glob(os.path.join(cache_path, '*.png'))
464 444
 
465
-    with codecs.open(new_index_fullpath, 'w', 'utf-8') as f:
466
-        f.write(html_head % (even_background, odd_background))
467
-
468
-        f.write('<div align="center">\n<table class="legend">\n<tr><th></th><th>Median</th><th>Mean</th><th>Std. Dev</th><th>Threshold</th></tr>\n')
469
-        f.write('<tr><th>Weekday</th><td>%1.1f</td><td>%1.1f</td><td>%1.1f</td><td>%1.1f</td></tr>\n' % (weekday_stats[2][0], weekday_stats[2][1], weekday_stats[2][2], weekday_stats[2][1] + weekday_stats[2][2]))
470
-        f.write('<tr><th>Weekend</th><td>%1.1f</td><td>%1.1f</td><td>%1.1f</td><td>%1.1f</td></tr>\n' % (weekend_stats[2][0], weekend_stats[2][1], weekend_stats[2][2], weekend_stats[2][1] + weekend_stats[2][2]))
471
-        f.write('</table></div>\n<br />\n')
472
-
473
-        f.write('<div align="center">\n<table>\n')
445
+    chart_io = cStringIO.StringIO()
474 446
     for image_index, image in enumerate(yaml_items[:40]):
475 447
         tag_hit = False
476 448
         if image['author'].lower() in authors_to_post:
477 449
             tag_hit = True
478 450
         elif len(set([j.lower() for j in image['tags']]) & tags_to_post) > 0:
479 451
             tag_hit = True
480
-            chart_url = make_chart_url(image['orig_posted'],
452
+        write_chart_data(image['orig_posted'],
481 453
                          image['comment_times'],
482 454
                          image['fb_comments'],
483 455
                          image[rhs_metric_times],
484 456
                          image[rhs_metric],
485 457
                          image['qualified'],
486
-                                       image_index % 2,
487
-                                       tag_hit
458
+                         image_index,
459
+                         tag_hit,
460
+                         chart_io
488 461
                         )
489
-            image_url = save_image(chart_url, os.path.join(cache_path, '%d_%d.png' % (cur_time, image_index)))
462
+
463
+    with codecs.open(new_index_fullpath, 'w', 'utf-8') as f:
464
+        f.write(html_head % (even_background, odd_background, chart_io.getvalue()))
465
+        chart_io.close()
466
+        f.write('<div align="center">\n<table class="legend">\n<tr><th></th><th>Median</th><th>Mean</th><th>Std. Dev</th><th>Threshold</th></tr>\n')
467
+        f.write('<tr><th>Weekday</th><td>%1.1f</td><td>%1.1f</td><td>%1.1f</td><td>%1.1f</td></tr>\n' % (weekday_stats[2][0], weekday_stats[2][1], weekday_stats[2][2], weekday_stats[2][1] + weekday_stats[2][2]))
468
+        f.write('<tr><th>Weekend</th><td>%1.1f</td><td>%1.1f</td><td>%1.1f</td><td>%1.1f</td></tr>\n' % (weekend_stats[2][0], weekend_stats[2][1], weekend_stats[2][2], weekend_stats[2][1] + weekend_stats[2][2]))
469
+        f.write('</table></div>\n<br />\n')
470
+        f.write('<div align="center">\n<table>\n')
471
+        for image_index, image in enumerate(yaml_items[:40]):
490 472
             f.write('<tr valign="center" class="%s">\n  <td><strong><a href="%s">%s</a></strong> <span class="author">by %s</span></td>\n' % \
491 473
                      (image_index % 2 and "even" or "odd",
492 474
                       image['link'],
... ...
@@ -495,12 +477,7 @@ def make_index_html(yaml_items, weekend_stats, weekday_stats):
495 477
                      )
496 478
                    )
497 479
             f.write('  <td>%s<td>\n' % (image['qualified'] != -1 and '<img src="star_30.png" width="30" height="29" />' or ''))
498
-            f.write('  <td><img src="%s" width="%d" height="%d" border="0" /></td></tr>\n' % \
499
-                     (image_url,
500
-                      img_width,
501
-                      img_height
502
-                     )
503
-                   )
480
+            f.write('  <td><div id="chart%d" /></td></tr>\n' % (image_index, ))
504 481
         f.write(html_footer)
505 482
 
506 483
     if os.path.exists(index_fullpath):
507 484